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 ro...@apache.org on 2016/08/31 08:06:53 UTC

[04/10] james-project git commit: MAILBOX-270: getmetadata command, add new definition for command parser and processor.

MAILBOX-270: getmetadata command, add new definition for command parser and processor.


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

Branch: refs/heads/master
Commit: ced2d52fe2085596e7fdd2f5cd0e8953176faf18
Parents: 709cd70
Author: Quynh Nguyen <qn...@linagora.com>
Authored: Tue Jul 5 13:31:46 2016 +0700
Committer: Quynh Nguyen <qn...@linagora.com>
Committed: Tue Aug 30 09:21:30 2016 +0700

----------------------------------------------------------------------
 .../apache/james/imap/api/ImapConstants.java    |   4 +
 .../parser/GetAnnotationCommandParser.java      | 131 +++++++
 .../imap/decode/parser/ImapParserFactory.java   |   3 +-
 .../message/request/GetAnnotationRequest.java   | 158 ++++++++
 .../imap/processor/DefaultProcessorChain.java   |   4 +-
 .../imap/processor/GetAnnotationProcessor.java  | 175 +++++++++
 .../message/response/StatusResponseTest.java    |   3 +-
 .../parser/GetAnnotationCommandParserTest.java  | 390 +++++++++++++++++++
 .../processor/GetAnnotationProcessorTest.java   | 329 ++++++++++++++++
 protocols/src/site/xdoc/imap4.xml               |   5 +-
 10 files changed, 1196 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/ced2d52f/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java
----------------------------------------------------------------------
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java b/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java
index 70d1b22..0d9ef2f 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java
@@ -239,6 +239,8 @@ public interface ImapConstants {
 
     String SETANNOTATION_COMMAND_NAME = "SETMETADATA";
 
+    String GETANNOTATION_COMMAND_NAME = "GETMETADATA";
+
     String LIST_RESPONSE_NAME = "LIST";
 
     String XLIST_RESPONSE_NAME = "XLIST";
@@ -257,6 +259,8 @@ public interface ImapConstants {
     
     String MYRIGHTS_RESPONSE_NAME = "MYRIGHTS";
 
+    String ANNOTATION_RESPONSE_NAME = "METADATA";
+
     String NAME_ATTRIBUTE_NOINFERIORS = "\\Noinferiors";
 
     String NAME_ATTRIBUTE_NOSELECT = "\\Noselect";

http://git-wip-us.apache.org/repos/asf/james-project/blob/ced2d52f/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/GetAnnotationCommandParser.java
----------------------------------------------------------------------
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/GetAnnotationCommandParser.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/GetAnnotationCommandParser.java
new file mode 100644
index 0000000..07cf408
--- /dev/null
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/GetAnnotationCommandParser.java
@@ -0,0 +1,131 @@
+/****************************************************************
+ * 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.imap.decode.parser;
+
+import com.google.common.base.Optional;
+import org.apache.james.imap.message.request.GetAnnotationRequest;
+import org.apache.james.mailbox.model.MailboxAnnotationKey;
+import com.google.common.base.CharMatcher;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import org.apache.james.imap.api.ImapCommand;
+import org.apache.james.imap.api.ImapConstants;
+import org.apache.james.imap.api.ImapMessage;
+import org.apache.james.imap.api.display.HumanReadableText;
+import org.apache.james.imap.api.process.ImapSession;
+import org.apache.james.imap.decode.ImapRequestLineReader;
+import org.apache.james.imap.decode.base.AbstractImapCommandParser;
+import org.apache.james.imap.message.request.GetAnnotationRequest.Depth;
+import org.apache.james.protocols.imap.DecodingException;
+
+public class GetAnnotationCommandParser extends AbstractImapCommandParser {
+    private static final CharMatcher ENDOFLINE_PATTERN = CharMatcher.isNot('\n').and(CharMatcher.isNot('\r'));
+    private static final  String MAXSIZE = "MAXSIZE";
+    private static final String DEPTH = "DEPTH";
+    private static final boolean STOP_ON_PAREN = true;
+
+    public GetAnnotationCommandParser() {
+        super(ImapCommand.authenticatedStateCommand(ImapConstants.GETANNOTATION_COMMAND_NAME));
+    }
+
+    @Override
+    protected ImapMessage decode(ImapCommand command, ImapRequestLineReader requestReader, String tag, ImapSession session)
+        throws DecodingException {
+        try {
+            return buildAnnotationRequest(command, requestReader, tag);
+        } catch (NullPointerException e) {
+            throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, e.getMessage());
+        } catch (IllegalArgumentException e) {
+            throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, e.getMessage());
+        } catch (IllegalStateException e) {
+            throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, e.getMessage());
+        }
+    }
+
+    private ImapMessage buildAnnotationRequest(ImapCommand command, ImapRequestLineReader requestReader, String tag) throws DecodingException {
+        GetAnnotationRequest.Builder builder = GetAnnotationRequest.builder()
+            .tag(tag)
+            .command(command)
+            .mailboxName(requestReader.mailbox());
+
+        consumeOptionsAndKeys(requestReader, builder);
+
+        if (ENDOFLINE_PATTERN.matches(requestReader.nextNonSpaceChar())) {
+            consumeKey(requestReader, builder);
+        }
+
+        return builder.build();
+    }
+
+    private void consumeOptionsAndKeys(ImapRequestLineReader requestReader, GetAnnotationRequest.Builder builder) throws DecodingException {
+        while (requestReader.nextNonSpaceChar() == '(') {
+            requestReader.consumeChar('(');
+            switch (requestReader.nextChar()) {
+                case 'M':
+                    consumeMaxsizeOpt(requestReader, builder);
+                    break;
+
+                case 'D':
+                    consumeDepthOpt(requestReader, builder);
+                    break;
+
+                default:
+                    consumeKeys(requestReader, builder);
+                    break;
+            }
+        }
+    }
+
+    private void consumeDepthOpt(ImapRequestLineReader requestReader, GetAnnotationRequest.Builder builder) throws DecodingException {
+        if (!requestReader.atom().equalsIgnoreCase(DEPTH)) {
+            throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Wrong on, it should be DEPTH");
+        }
+
+        builder.depth(Depth.fromString(requestReader.atom()));
+        requestReader.consumeChar(')');
+    }
+
+    private void consumeMaxsizeOpt(ImapRequestLineReader requestReader, GetAnnotationRequest.Builder builder) throws DecodingException {
+        if (!requestReader.atom().equalsIgnoreCase(MAXSIZE)) {
+            throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Wrong on, it should be MAXSIZE");
+        }
+
+        builder.maxsize(Optional.of((int)requestReader.number(STOP_ON_PAREN)));
+        requestReader.consumeChar(')');
+    }
+
+    private void consumeKey(ImapRequestLineReader requestReader, GetAnnotationRequest.Builder builder) throws DecodingException {
+        builder.keys(ImmutableSet.of(new MailboxAnnotationKey(requestReader.atom())));
+        requestReader.eol();
+    }
+
+    private void consumeKeys(ImapRequestLineReader requestReader, GetAnnotationRequest.Builder builder) throws DecodingException {
+        Builder<MailboxAnnotationKey> keys = ImmutableSet.builder();
+
+        do {
+            keys.add(new MailboxAnnotationKey(requestReader.atom()));
+        } while (requestReader.nextWordChar() != ')');
+
+        builder.keys(keys.build());
+
+        requestReader.consumeChar(')');
+        requestReader.eol();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/ced2d52f/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java
----------------------------------------------------------------------
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java
index ea57de6..c133f51 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java
@@ -108,8 +108,9 @@ public class ImapParserFactory implements ImapCommandParserFactory {
         _imapCommands.put(ImapConstants.SETQUOTA_COMMAND_NAME, SetQuotaCommandParser.class);
 
         //RFC5464
-        //SETMETADATA
+        //SETMETADATA, GETMETADATA
         _imapCommands.put(ImapConstants.SETANNOTATION_COMMAND_NAME, SetAnnotationCommandParser.class);
+        _imapCommands.put(ImapConstants.GETANNOTATION_COMMAND_NAME, GetAnnotationCommandParser.class);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/james-project/blob/ced2d52f/protocols/imap/src/main/java/org/apache/james/imap/message/request/GetAnnotationRequest.java
----------------------------------------------------------------------
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/message/request/GetAnnotationRequest.java b/protocols/imap/src/main/java/org/apache/james/imap/message/request/GetAnnotationRequest.java
new file mode 100644
index 0000000..0da09fc
--- /dev/null
+++ b/protocols/imap/src/main/java/org/apache/james/imap/message/request/GetAnnotationRequest.java
@@ -0,0 +1,158 @@
+/****************************************************************
+ * 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.imap.message.request;
+
+import java.util.Set;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+import org.apache.james.imap.api.ImapCommand;
+
+import com.google.common.base.Preconditions;
+import org.apache.james.mailbox.model.MailboxAnnotationKey;
+
+public class GetAnnotationRequest extends AbstractImapRequest {
+    public static class Builder {
+        private String tag;
+        private ImapCommand command;
+        private String mailboxName;
+        private Set<MailboxAnnotationKey> keys;
+        private Optional<Integer> maxsize;
+        private Depth depth;
+
+        private Builder() {
+            this.depth = Depth.ZERO;
+            this.maxsize = Optional.absent();
+            keys = ImmutableSet.of();
+        }
+
+        public Builder tag(String tag) {
+            this.tag = tag;
+            return this;
+        }
+
+        public Builder command(ImapCommand command) {
+            this.command = command;
+            return this;
+        }
+
+        public Builder mailboxName(String mailboxName) {
+            Preconditions.checkNotNull(mailboxName);
+            this.mailboxName = mailboxName;
+            return this;
+        }
+
+        public Builder keys(Set<MailboxAnnotationKey> keys) {
+            this.keys = ImmutableSet.copyOf(keys);
+            return this;
+        }
+
+        public Builder maxsize(Optional<Integer> maxsize) {
+            this.maxsize = maxsize;
+            return this;
+        }
+
+        public Builder depth(Depth depth) {
+            this.depth = depth;
+            return this;
+        }
+
+        public GetAnnotationRequest build() {
+            Preconditions.checkState(isNoDepth() || isDepthAndKeysNotEmpty());
+            Preconditions.checkArgument(isNoMaxsize() || maxsize.get() > 0);
+            return new GetAnnotationRequest(this);
+        }
+
+        private boolean isDepthAndKeysNotEmpty() {
+            return !Depth.ZERO.equals(depth) && !keys.isEmpty();
+        }
+
+        private boolean isNoDepth() {
+            return Depth.ZERO.equals(depth);
+        }
+
+        private boolean isNoMaxsize() {
+            return !maxsize.isPresent();
+        }
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    private final String mailboxName;
+    private final Set<MailboxAnnotationKey> keys;
+    private final Optional<Integer> maxsize;
+    private final Depth depth;
+
+    private GetAnnotationRequest(Builder builder) {
+        super(builder.tag, builder.command);
+        this.mailboxName = builder.mailboxName;
+        this.depth = builder.depth;
+        this.maxsize = builder.maxsize;
+        this.keys = builder.keys;
+    }
+
+    public String getMailboxName() {
+        return mailboxName;
+    }
+
+    public Set<MailboxAnnotationKey> getKeys() {
+        return keys;
+    }
+
+    public Optional<Integer> getMaxsize() {
+        return maxsize;
+    }
+
+    public Depth getDepth() {
+        return depth;
+    }
+
+    public enum Depth {
+        ZERO("0"), ONE("1"), INFINITY("infinity");
+
+        private final String code;
+
+        Depth(String code) {
+            this.code = code;
+        }
+
+        public final String getCode() {
+            return code;
+        }
+
+        public String toString() {
+            return code;
+        }
+
+        public static Depth fromString(String code) {
+            Preconditions.checkNotNull(code);
+
+            for (Depth depth : Depth.values()) {
+                if (code.equalsIgnoreCase(depth.code)) {
+                    return depth;
+                }
+            }
+
+            throw new IllegalArgumentException("Cannot lookup Depth data for: " + code);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/ced2d52f/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessorChain.java
----------------------------------------------------------------------
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessorChain.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessorChain.java
index c8a8ceb..31b20b8 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessorChain.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessorChain.java
@@ -59,7 +59,9 @@ public class DefaultProcessorChain {
         if (mailboxManager.hasCapability(MailboxManager.MailboxCapabilities.Annotation)) {
             final SetAnnotationProcessor setAnnotationProcessor = new SetAnnotationProcessor(unsubscribeProcessor, mailboxManager, statusResponseFactory);
             capabilityProcessor.addProcessor(setAnnotationProcessor);
-            subscribeProcessor = new SubscribeProcessor(setAnnotationProcessor, mailboxManager, subscriptionManager, statusResponseFactory);
+            final GetAnnotationProcessor getAnnotationProcessor = new GetAnnotationProcessor(setAnnotationProcessor, mailboxManager, statusResponseFactory);
+            capabilityProcessor.addProcessor(getAnnotationProcessor);
+            subscribeProcessor = new SubscribeProcessor(getAnnotationProcessor, mailboxManager, subscriptionManager, statusResponseFactory);
         } else {
             subscribeProcessor = new SubscribeProcessor(unsubscribeProcessor, mailboxManager, subscriptionManager, statusResponseFactory);
         }

http://git-wip-us.apache.org/repos/asf/james-project/blob/ced2d52f/protocols/imap/src/main/java/org/apache/james/imap/processor/GetAnnotationProcessor.java
----------------------------------------------------------------------
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/GetAnnotationProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/GetAnnotationProcessor.java
new file mode 100644
index 0000000..052e533
--- /dev/null
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/GetAnnotationProcessor.java
@@ -0,0 +1,175 @@
+/****************************************************************
+ * 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.imap.processor;
+
+import static org.apache.james.imap.api.message.response.StatusResponse.ResponseCode;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedSet;
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.james.imap.api.ImapCommand;
+import org.apache.james.imap.api.ImapConstants;
+import org.apache.james.imap.api.ImapSessionUtils;
+import org.apache.james.imap.api.display.HumanReadableText;
+import org.apache.james.imap.api.message.response.StatusResponseFactory;
+import org.apache.james.imap.api.process.ImapProcessor;
+import org.apache.james.imap.api.process.ImapSession;
+import org.apache.james.imap.message.request.GetAnnotationRequest;
+import org.apache.james.imap.message.response.AnnotationResponse;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.exception.MailboxNotFoundException;
+import org.apache.james.mailbox.model.MailboxAnnotation;
+import org.apache.james.mailbox.model.MailboxAnnotationKey;
+import org.apache.james.mailbox.model.MailboxPath;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+public class GetAnnotationProcessor extends AbstractMailboxProcessor<GetAnnotationRequest> implements CapabilityImplementingProcessor {
+    public GetAnnotationProcessor(ImapProcessor next, MailboxManager mailboxManager, StatusResponseFactory factory) {
+        super(GetAnnotationRequest.class, next, mailboxManager, factory);
+    }
+
+    public List<String> getImplementedCapabilities(ImapSession session) {
+        return ImmutableList.of(ImapConstants.SUPPORTS_ANNOTATION);
+    }
+
+    protected void doProcess(GetAnnotationRequest message, ImapSession session, String tag, ImapCommand command,
+            Responder responder) {
+        try {
+            proceed(message, session, tag, command, responder);
+        } catch (MailboxNotFoundException e) {
+            session.getLog().info("The command: {} is failed because not found mailbox {}", command.getName(), message.getMailboxName());
+            no(command, tag, responder, HumanReadableText.FAILURE_NO_SUCH_MAILBOX, ResponseCode.tryCreate());
+        } catch (MailboxException e) {
+            session.getLog().info("The command: {} on mailbox {} is failed", command.getName(), message.getMailboxName());
+            no(command, tag, responder, HumanReadableText.GENERIC_FAILURE_DURING_PROCESSING);
+        }
+    }
+
+    private void proceed(GetAnnotationRequest message, ImapSession session, String tag, ImapCommand command, Responder responder) throws MailboxException {
+        String mailboxName = message.getMailboxName();
+        Optional<Integer> maxsize = message.getMaxsize();
+        MailboxPath mailboxPath = buildFullPath(session, mailboxName);
+
+        List<MailboxAnnotation> mailboxAnnotations = getMailboxAnnotations(session, message.getKeys(), message.getDepth(), mailboxPath);
+        Optional<Integer> maximumOversizedSize = getMaxSizeValue(mailboxAnnotations, maxsize);
+
+        respond(tag, command, responder, mailboxName, mailboxAnnotations, maxsize, maximumOversizedSize);
+    }
+
+    private void respond(String tag, ImapCommand command, Responder responder, String mailboxName,
+                         List<MailboxAnnotation> mailboxAnnotations, Optional<Integer> maxsize, Optional<Integer> maximumOversizedSize) {
+        if (maximumOversizedSize.isPresent()) {
+            responder.respond(new AnnotationResponse(mailboxName, filterItemsBySize(responder, mailboxName, mailboxAnnotations, maxsize)));
+            okComplete(command, tag, ResponseCode.longestMetadataEntry(maximumOversizedSize.get()), responder);
+        } else {
+            responder.respond(new AnnotationResponse(mailboxName, mailboxAnnotations));
+            okComplete(command, tag, responder);
+        }
+    }
+
+    private Optional<Integer> getMaxSizeValue(final List<MailboxAnnotation> mailboxAnnotations, Optional<Integer> maxsize) {
+        if (maxsize.isPresent()) {
+            return maxsize.transform(new Function<Integer, Optional<Integer>>() {
+                @Override
+                public Optional<Integer> apply(Integer input) {
+                    return getMaxSizeOfOversizedItems(mailboxAnnotations, input);
+                }
+            }).get();
+        }
+        return Optional.absent();
+    }
+
+    private List<MailboxAnnotation> filterItemsBySize(Responder responder, String mailboxName, List<MailboxAnnotation> mailboxAnnotations, final Optional<Integer> maxsize) {
+        Predicate<MailboxAnnotation> lowerPredicate = new Predicate<MailboxAnnotation>() {
+            @Override
+            public boolean apply(final MailboxAnnotation input) {
+                return maxsize.transform(new Function<Integer, Boolean>() {
+                    @Override
+                    public Boolean apply(Integer maxSizeInput) {
+                        return (input.size() <= maxSizeInput);
+                    }
+                }).or(true);
+            }
+        };
+
+        return FluentIterable.from(mailboxAnnotations).filter(lowerPredicate).toList();
+    }
+
+    private List<MailboxAnnotation> getMailboxAnnotations(ImapSession session, Set<MailboxAnnotationKey> keys, GetAnnotationRequest.Depth depth, MailboxPath mailboxPath) throws MailboxException {
+        MailboxSession mailboxSession = ImapSessionUtils.getMailboxSession(session);
+        switch (depth) {
+            case ZERO:
+                return getMailboxAnnotationsWithDepthZero(keys, mailboxPath, mailboxSession);
+            case ONE:
+                return getMailboxManager().getAnnotationsByKeysWithOneDepth(mailboxPath, mailboxSession, keys);
+            case INFINITY:
+                return getMailboxManager().getAnnotationsByKeysWithAllDepth(mailboxPath, mailboxSession, keys);
+            default:
+                throw new NotImplementedException();
+        }
+    }
+
+    private List<MailboxAnnotation> getMailboxAnnotationsWithDepthZero(Set<MailboxAnnotationKey> keys, MailboxPath mailboxPath, MailboxSession mailboxSession) throws MailboxException {
+        if (keys.isEmpty()) {
+            return getMailboxManager().getAllAnnotations(mailboxPath, mailboxSession);
+        } else {
+            return getMailboxManager().getAnnotationsByKeys(mailboxPath, mailboxSession, keys);
+        }
+    }
+
+    private Optional<Integer> getMaxSizeOfOversizedItems(List<MailboxAnnotation> mailboxAnnotations, final Integer maxsize) {
+        Predicate<MailboxAnnotation> filterOverSizedAnnotation = new Predicate<MailboxAnnotation>() {
+            @Override
+            public boolean apply(MailboxAnnotation input) {
+                return (input.size() > maxsize);
+            }
+        };
+
+        Function<MailboxAnnotation, Integer> transformToSize = new Function<MailboxAnnotation,Integer>(){
+            public Integer apply(MailboxAnnotation input) {
+                return input.size();
+            }
+        };
+
+        ImmutableSortedSet<Integer> overLimitSizes = FluentIterable.from(mailboxAnnotations)
+            .filter(filterOverSizedAnnotation)
+            .transform(transformToSize)
+            .toSortedSet(new Comparator<Integer>() {
+                @Override
+                public int compare(Integer annotationSize1, Integer annotationSize2) {
+                    return annotationSize2.compareTo(annotationSize1);
+                }
+            });
+
+        if (overLimitSizes.isEmpty()) {
+            return Optional.absent();
+        }
+        return Optional.of(overLimitSizes.first());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/ced2d52f/protocols/imap/src/test/java/org/apache/james/imap/api/message/response/StatusResponseTest.java
----------------------------------------------------------------------
diff --git a/protocols/imap/src/test/java/org/apache/james/imap/api/message/response/StatusResponseTest.java b/protocols/imap/src/test/java/org/apache/james/imap/api/message/response/StatusResponseTest.java
index e650685..adcfccb 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/api/message/response/StatusResponseTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/api/message/response/StatusResponseTest.java
@@ -19,9 +19,8 @@
 
 package org.apache.james.imap.api.message.response;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
-import org.apache.james.imap.api.message.response.StatusResponse;
 import org.junit.Test;
 
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/ced2d52f/protocols/imap/src/test/java/org/apache/james/imap/decode/parser/GetAnnotationCommandParserTest.java
----------------------------------------------------------------------
diff --git a/protocols/imap/src/test/java/org/apache/james/imap/decode/parser/GetAnnotationCommandParserTest.java b/protocols/imap/src/test/java/org/apache/james/imap/decode/parser/GetAnnotationCommandParserTest.java
new file mode 100644
index 0000000..f8ee791
--- /dev/null
+++ b/protocols/imap/src/test/java/org/apache/james/imap/decode/parser/GetAnnotationCommandParserTest.java
@@ -0,0 +1,390 @@
+/****************************************************************
+ * 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.imap.decode.parser;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.guava.api.Assertions.assertThat;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import com.google.common.base.Charsets;
+import org.apache.james.imap.api.ImapCommand;
+import org.apache.james.imap.api.process.ImapSession;
+import org.apache.james.imap.decode.ImapRequestStreamLineReader;
+import org.apache.james.imap.message.request.GetAnnotationRequest;
+import org.apache.james.imap.message.request.GetAnnotationRequest.Depth;
+import org.apache.james.mailbox.model.MailboxAnnotationKey;
+import org.apache.james.protocols.imap.DecodingException;
+import org.junit.Before;
+import org.junit.Test;
+
+public class GetAnnotationCommandParserTest {
+
+    private static final String INBOX = "anyInboxName";
+    private static final String TAG = "A1";
+    private static final MailboxAnnotationKey PRIVATE_KEY = new MailboxAnnotationKey("/private/comment");
+    private static final MailboxAnnotationKey SHARED_KEY = new MailboxAnnotationKey("/shared/comment");
+    private static final ImapCommand command = ImapCommand.anyStateCommand("Command");
+    private static final ImapSession session = null;
+    private static final OutputStream outputStream = null;
+
+    private GetAnnotationCommandParser parser;
+
+    @Before
+    public void setUp() throws Exception {
+        parser = new GetAnnotationCommandParser();
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowsExceptionWhenCommandHasNotMailbox() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream(" \n".getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test
+    public void decodeMessageShouldReturnRequestWhenCommandHasMailboxOnly() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + "    \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        GetAnnotationRequest request = (GetAnnotationRequest) parser.decode(command, lineReader, TAG, session);
+
+        assertThat(request.getTag()).isEqualTo(TAG);
+        assertThat(request.getCommand()).isEqualTo(command);
+        assertThat(request.getMailboxName()).isEqualTo(INBOX);
+        assertThat(request.getKeys()).isEmpty();
+        assertThat(request.getDepth()).isEqualTo(Depth.ZERO);
+        assertThat(request.getMaxsize()).isAbsent();
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasOneKeyButInWrongFormat() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " /private/comment extrastring \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test
+    public void decodeMessageShouldReturnRequestWhenCommandHasOnlyOneKey() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " /private/comment \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        GetAnnotationRequest request = (GetAnnotationRequest) parser.decode(command, lineReader, TAG, session);
+
+        assertThat(request.getTag()).isEqualTo(TAG);
+        assertThat(request.getCommand()).isEqualTo(command);
+        assertThat(request.getMailboxName()).isEqualTo(INBOX);
+        assertThat(request.getKeys()).containsOnly(PRIVATE_KEY);
+        assertThat(request.getDepth()).isEqualTo(Depth.ZERO);
+        assertThat(request.getMaxsize()).isAbsent();
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasOneInvalidKey() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + "/shared/comment private/comment \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test
+    public void decodeMessageShouldReturnRequestWhenCommandHasMultiKeys() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        GetAnnotationRequest request = (GetAnnotationRequest) parser.decode(command, lineReader, TAG, session);
+
+        assertThat(request.getTag()).isEqualTo(TAG);
+        assertThat(request.getCommand()).isEqualTo(command);
+        assertThat(request.getMailboxName()).isEqualTo(INBOX);
+        assertThat(request.getKeys()).contains(PRIVATE_KEY, SHARED_KEY);
+        assertThat(request.getDepth()).isEqualTo(Depth.ZERO);
+        assertThat(request.getMaxsize()).isAbsent();
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasMultiKeysButInWrongFormat() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (/shared/comment /private/comment) (/another/key/group)\n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasMultiKeysAndSingleKey() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (/shared/comment /private/comment) /another/key \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasMultiKeysButNotOpenQuote() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " /shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasMultiKeysButNotCloseQuote() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (/shared/comment /private/comment \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasMaxsizeOptButInWrongPlace() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (/shared/comment /private/comment) (MAXSIZE 1024) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasMaxsizeWithWrongValue() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (MAXSIZE invalid) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasMaxsizeWithoutValue() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (MAXSIZE) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasMaxsizeDoesNotInParenthesis() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " MAXSIZE 1024 (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasMaxsizeDoesNotInParenthesisAndNoValue() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " MAXSIZE (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test
+    public void decodeMessageShouldReturnRequestWhenCommandHasMaxsizeOption() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (MAXSIZE 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        GetAnnotationRequest request = (GetAnnotationRequest) parser.decode(command, lineReader, TAG, session);
+
+        assertThat(request.getTag()).isEqualTo(TAG);
+        assertThat(request.getCommand()).isEqualTo(command);
+        assertThat(request.getMailboxName()).isEqualTo(INBOX);
+        assertThat(request.getKeys()).contains(PRIVATE_KEY, SHARED_KEY);
+        assertThat(request.getDepth()).isEqualTo(Depth.ZERO);
+        assertThat(request.getMaxsize()).contains(1024);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldReturnRequestWhenCommandHasWrongMaxsizeOption() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (MAXSIZErr 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldReturnRequestWhenCommandHasWrongMaxsizeValue() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (MAXSIZE 0) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldReturnRequestWhenCommandHasWrongDepthOption() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (DEPTH -1) (MAXSIZE 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldReturnRequestWhenCommandHasWrongDepthOptionName() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (DEPTHerr 1) (MAXSIZE 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldReturnRequestWhenCommandHasDepthOptionButNoValue() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (DEPTH) (MAXSIZE 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldReturnRequestWhenCommandHasDepthOptionButInvalidValue() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (DEPTH invalid) (MAXSIZE 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldReturnRequestWhenCommandHasDepthOptionButNotInParenthesis() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " DEPTH (MAXSIZE 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldReturnRequestWhenCommandHasDepthOptionAndValueButNotInParenthesis() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " DEPTH 1 (MAXSIZE 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test
+    public void decodeMessageShouldReturnRequestWithZeroDepthOption() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (DEPTH 0) (MAXSIZE 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        GetAnnotationRequest request = (GetAnnotationRequest)parser.decode(command, lineReader, TAG, null);
+
+        assertThat(request.getTag()).isEqualTo(TAG);
+        assertThat(request.getCommand()).isEqualTo(command);
+        assertThat(request.getMailboxName()).isEqualTo(INBOX);
+        assertThat(request.getDepth()).isEqualTo(Depth.ZERO);
+        assertThat(request.getMaxsize()).contains(1024);
+        assertThat(request.getKeys()).contains(SHARED_KEY, PRIVATE_KEY);
+    }
+
+    @Test
+    public void decodeMessageShouldReturnRequestWithOneDepthOption() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (DEPTH 1) (MAXSIZE 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        GetAnnotationRequest request = (GetAnnotationRequest)parser.decode(command, lineReader, TAG, session);
+
+        assertThat(request.getTag()).isEqualTo(TAG);
+        assertThat(request.getCommand()).isEqualTo(command);
+        assertThat(request.getMailboxName()).isEqualTo(INBOX);
+        assertThat(request.getDepth()).isEqualTo(Depth.ONE);
+        assertThat(request.getMaxsize()).contains(1024);
+        assertThat(request.getKeys()).contains(SHARED_KEY, PRIVATE_KEY);
+    }
+
+    @Test
+    public void decodeMessageShouldReturnRequestWhenCommandHasOptionsInAnyOrder() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (MAXSIZE 1024) (DEPTH 1) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        GetAnnotationRequest request = (GetAnnotationRequest)parser.decode(command, lineReader, TAG, session);
+
+        assertThat(request.getTag()).isEqualTo(TAG);
+        assertThat(request.getCommand()).isEqualTo(command);
+        assertThat(request.getMailboxName()).isEqualTo(INBOX);
+        assertThat(request.getDepth()).isEqualTo(Depth.ONE);
+        assertThat(request.getMaxsize()).contains(1024);
+        assertThat(request.getKeys()).contains(SHARED_KEY, PRIVATE_KEY);
+    }
+
+    @Test
+    public void decodeMessageShouldReturnRequestWithInfinityDepthOption() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (DEPTH infinity) (MAXSIZE 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        GetAnnotationRequest request = (GetAnnotationRequest)parser.decode(command, lineReader, TAG, session);
+
+        assertThat(request.getTag()).isEqualTo(TAG);
+        assertThat(request.getCommand()).isEqualTo(command);
+        assertThat(request.getMailboxName()).isEqualTo(INBOX);
+        assertThat(request.getDepth()).isEqualTo(Depth.INFINITY);
+        assertThat(request.getMaxsize()).contains(1024);
+        assertThat(request.getKeys()).contains(SHARED_KEY, PRIVATE_KEY);
+    }
+
+    @Test
+    public void decodeMessageShouldReturnRequestWithOnlyInfinityDepthOption() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (DEPTH infinity) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        GetAnnotationRequest request = (GetAnnotationRequest)parser.decode(command, lineReader, TAG, session);
+
+        assertThat(request.getTag()).isEqualTo(TAG);
+        assertThat(request.getCommand()).isEqualTo(command);
+        assertThat(request.getMailboxName()).isEqualTo(INBOX);
+        assertThat(request.getDepth()).isEqualTo(Depth.INFINITY);
+        assertThat(request.getMaxsize()).isAbsent();
+        assertThat(request.getKeys()).contains(SHARED_KEY, PRIVATE_KEY);
+    }
+
+    @Test
+    public void decodeMessageShouldReturnRequestWithDefaultDepthOptionWhenCommandHasDoesNotHaveDepthOption() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (MAXSIZE 1024) (/shared/comment /private/comment) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        GetAnnotationRequest request = (GetAnnotationRequest)parser.decode(command, lineReader, TAG, session);
+
+        assertThat(request.getTag()).isEqualTo(TAG);
+        assertThat(request.getCommand()).isEqualTo(command);
+        assertThat(request.getMailboxName()).isEqualTo(INBOX);
+        assertThat(request.getDepth()).isEqualTo(Depth.ZERO);
+        assertThat(request.getMaxsize()).contains(1024);
+        assertThat(request.getKeys()).contains(SHARED_KEY, PRIVATE_KEY);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasOneDepthButWithoutKey() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (DEPTH 1) (MAXSIZE 1024) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasInfinityDepthButWithoutKey() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (DEPTH infinity) (MAXSIZE 1024) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+
+    @Test(expected = DecodingException.class)
+    public void decodeMessageShouldThrowExceptionWhenCommandHasDepthOptionInWrongPlace() throws DecodingException {
+        InputStream inputStream = new ByteArrayInputStream((INBOX + " (/shared/comment /private/comment) (DEPTH infinity) \n").getBytes(Charsets.US_ASCII));
+        ImapRequestStreamLineReader lineReader = new ImapRequestStreamLineReader(inputStream, outputStream);
+
+        parser.decode(command, lineReader, TAG, session);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/ced2d52f/protocols/imap/src/test/java/org/apache/james/imap/processor/GetAnnotationProcessorTest.java
----------------------------------------------------------------------
diff --git a/protocols/imap/src/test/java/org/apache/james/imap/processor/GetAnnotationProcessorTest.java b/protocols/imap/src/test/java/org/apache/james/imap/processor/GetAnnotationProcessorTest.java
new file mode 100644
index 0000000..5e4fd0d
--- /dev/null
+++ b/protocols/imap/src/test/java/org/apache/james/imap/processor/GetAnnotationProcessorTest.java
@@ -0,0 +1,329 @@
+/****************************************************************
+ * 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.imap.processor;
+
+import static org.apache.james.imap.api.message.response.StatusResponse.ResponseCode;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.apache.james.imap.api.ImapCommand;
+import org.apache.james.imap.api.ImapConstants;
+import org.apache.james.imap.api.ImapSessionState;
+import org.apache.james.imap.api.ImapSessionUtils;
+import org.apache.james.imap.api.display.HumanReadableText;
+import org.apache.james.imap.api.message.response.StatusResponse;
+import org.apache.james.imap.api.message.response.StatusResponseFactory;
+import org.apache.james.imap.api.process.ImapProcessor;
+import org.apache.james.imap.api.process.ImapSession;
+import org.apache.james.imap.encode.FakeImapSession;
+import org.apache.james.imap.message.request.GetAnnotationRequest;
+import org.apache.james.imap.message.request.GetAnnotationRequest.Depth;
+import org.apache.james.imap.message.response.AnnotationResponse;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.exception.MailboxNotFoundException;
+import org.apache.james.mailbox.mock.MockMailboxSession;
+import org.apache.james.mailbox.model.MailboxAnnotation;
+import org.apache.james.mailbox.model.MailboxAnnotationKey;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.MockitoAnnotations;
+import org.slf4j.Logger;
+
+import java.util.Set;
+
+public class GetAnnotationProcessorTest {
+    private static final String TAG = "TAG";
+    private static final int FIRST_ELEMENT_INDEX = 0;
+
+    private static final MailboxAnnotationKey PRIVATE_KEY = new MailboxAnnotationKey("/private/comment");
+    private static final MailboxAnnotationKey PRIVATE_CHILD_KEY = new MailboxAnnotationKey("/private/comment/user");
+    private static final MailboxAnnotationKey PRIVATE_GRANDCHILD_KEY = new MailboxAnnotationKey("/private/comment/user/name");
+    private static final MailboxAnnotationKey SHARED_KEY = new MailboxAnnotationKey("/shared/comment");
+
+    private static final MailboxAnnotation SHARED_ANNOTATION = MailboxAnnotation.newInstance(SHARED_KEY, "The shared size");
+    private static final MailboxAnnotation PRIVATE_ANNOTATION = MailboxAnnotation.newInstance(PRIVATE_KEY, "The short size");
+    private static final MailboxAnnotation PRIVATE_CHILD_ANNOTATION = MailboxAnnotation.newInstance(PRIVATE_CHILD_KEY, "The middle size");
+    private static final MailboxAnnotation PRIVATE_GRANDCHILD_ANNOTATION = MailboxAnnotation.newInstance(PRIVATE_GRANDCHILD_KEY, "The longest value size");
+
+    private GetAnnotationProcessor processor;
+
+    private ImapProcessor mockNextProcessor;
+    private MailboxManager mockMailboxManager;
+    private StatusResponseFactory mockStatusResponseFactory;
+    private ImapProcessor.Responder mockResponder;
+    private ImapSession mockImapSession;
+    private MailboxSession mailboxSession;
+
+    private Set<MailboxAnnotationKey> KEYS;
+    private StatusResponse statusResponse;
+
+    private GetAnnotationRequest.Builder annotationRequestBuilder;
+    private MailboxPath inbox;
+    private Logger log;
+    private ArgumentCaptor<HumanReadableText> humanTextCaptor;
+    private ArgumentCaptor<ResponseCode> captorResponsecode;
+    private ArgumentCaptor<AnnotationResponse> captorAnnotationResponse;
+
+    private void initAndMockData() {
+        statusResponse = mock(StatusResponse.class);
+        mockNextProcessor = mock(ImapProcessor.class);
+        mockMailboxManager = mock(MailboxManager.class);
+        mockStatusResponseFactory = mock(StatusResponseFactory.class);
+        mockResponder = mock(ImapProcessor.Responder.class);
+        mockImapSession = mock(ImapSession.class);
+        log = mock(Logger.class);
+
+        mailboxSession = new MockMailboxSession("username");
+        inbox = MailboxPath.inbox(mailboxSession);
+        KEYS = ImmutableSet.of(PRIVATE_KEY);
+        annotationRequestBuilder = GetAnnotationRequest.builder()
+            .tag(TAG)
+            .command(ImapCommand.anyStateCommand("Name"))
+            .mailboxName(ImapConstants.INBOX_NAME);
+        humanTextCaptor = ArgumentCaptor.forClass(HumanReadableText.class);
+        captorResponsecode = ArgumentCaptor.forClass(ResponseCode.class);
+        captorAnnotationResponse = ArgumentCaptor.forClass(AnnotationResponse.class);
+
+        when(mockImapSession.getState()).thenReturn(ImapSessionState.SELECTED);
+        when(mockImapSession.getAttribute(ImapSessionUtils.MAILBOX_SESSION_ATTRIBUTE_SESSION_KEY)).thenReturn(mailboxSession);
+        when(mockImapSession.getLog()).thenReturn(log);
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        initAndMockData();
+
+        processor = new GetAnnotationProcessor(mockNextProcessor, mockMailboxManager, mockStatusResponseFactory);
+    }
+
+    @Test
+    public void getImplementedCapabilitiesShouldContainSupportAnnotationWhenSupportedByMailboxManager() {
+        assertThat(processor.getImplementedCapabilities(new FakeImapSession())).containsExactly(ImapConstants.SUPPORTS_ANNOTATION);
+    }
+
+    @Test
+    public void processShouldResponseNoWithFailureWhenMailboxDoesNotExist() throws Exception {
+        doThrow(MailboxNotFoundException.class).when(mockMailboxManager).getAllAnnotations(eq(inbox), eq(mailboxSession));
+        when(mockStatusResponseFactory.taggedNo(any(String.class), any(ImapCommand.class), any(HumanReadableText.class), any(ResponseCode.class)))
+            .thenReturn(statusResponse);
+
+        processor.process(annotationRequestBuilder.build(), mockResponder, mockImapSession);
+
+        verify(mockStatusResponseFactory, times(1)).taggedNo(any(String.class), any(ImapCommand.class), humanTextCaptor.capture(), captorResponsecode.capture());
+        verify(mockResponder).respond(statusResponse);
+        verifyNoMoreInteractions(mockResponder);
+
+        assertThat(humanTextCaptor.getAllValues()).containsOnly(HumanReadableText.FAILURE_NO_SUCH_MAILBOX);
+        assertThat(captorResponsecode.getAllValues()).containsOnly(ResponseCode.tryCreate());
+    }
+
+    @Test
+    public void processShouldResponseNoWithGenericFailureWhenManagerThrowMailboxException() throws Exception {
+        doThrow(MailboxException.class).when(mockMailboxManager).getAllAnnotations(eq(inbox), eq(mailboxSession));
+        when(mockStatusResponseFactory.taggedNo(any(String.class), any(ImapCommand.class), any(HumanReadableText.class)))
+            .thenReturn(statusResponse);
+
+        processor.process(annotationRequestBuilder.build(), mockResponder, mockImapSession);
+
+        verify(mockStatusResponseFactory, times(1)).taggedNo(any(String.class), any(ImapCommand.class), humanTextCaptor.capture());
+        verify(mockResponder).respond(statusResponse);
+        verifyNoMoreInteractions(mockResponder);
+
+        assertThat(humanTextCaptor.getAllValues()).containsOnly(HumanReadableText.GENERIC_FAILURE_DURING_PROCESSING);
+    }
+
+    @Test
+    public void processShouldGetAllAnnotationsAndReturnCompleteResponse() throws Exception {
+        processor.process(annotationRequestBuilder.build(), mockResponder, mockImapSession);
+
+        verify(mockMailboxManager, times(1)).getAllAnnotations(inbox, mailboxSession);
+        verify(mockStatusResponseFactory, times(1)).taggedOk(any(String.class), any(ImapCommand.class), humanTextCaptor.capture());
+        verify(mockResponder, times(2)).respond(captorAnnotationResponse.capture());
+
+        verifyNoMoreInteractions(mockResponder);
+
+        assertThat(humanTextCaptor.getAllValues()).containsOnly(HumanReadableText.COMPLETED);
+    }
+
+    @Test
+    public void processShouldGetAnnotationsByKeysAndReturnCompleteResponse() throws Exception {
+        processor.process(annotationRequestBuilder.keys(KEYS).build(), mockResponder, mockImapSession);
+
+        verify(mockMailboxManager, times(1)).getAnnotationsByKeys(eq(inbox), eq(mailboxSession), eq(KEYS));
+        verify(mockStatusResponseFactory, times(1)).taggedOk(any(String.class), any(ImapCommand.class), humanTextCaptor.capture());
+        verify(mockResponder, times(2)).respond(captorAnnotationResponse.capture());
+        verifyNoMoreInteractions(mockResponder);
+
+        assertThat(humanTextCaptor.getAllValues()).containsOnly(HumanReadableText.COMPLETED);
+    }
+
+    @Test
+    public void processShouldGetAnnotationsAndReturnCompleteResponseWithTheLongestEntryInfoWhenLimitMaxsize() throws Exception {
+        when(mockMailboxManager.getAllAnnotations(inbox, mailboxSession)).thenReturn(ImmutableList.of(PRIVATE_ANNOTATION, SHARED_ANNOTATION, PRIVATE_GRANDCHILD_ANNOTATION));
+
+        processor.process(annotationRequestBuilder.maxsize(Optional.of(10)).build(), mockResponder, mockImapSession);
+
+        verify(mockStatusResponseFactory, times(1)).taggedOk(any(String.class),
+                any(ImapCommand.class),
+                humanTextCaptor.capture(),
+                captorResponsecode.capture());
+        verify(mockResponder, times(2)).respond(captorAnnotationResponse.capture());
+        verifyNoMoreInteractions(mockResponder);
+
+        assertThat(humanTextCaptor.getAllValues()).containsOnly(HumanReadableText.COMPLETED);
+        assertThat(captorResponsecode.getAllValues()).containsOnly(ResponseCode.longestMetadataEntry(22));
+    }
+
+    @Test
+    public void processShouldGetAnnotationsAndReturnCompleteResponseDoesNotTruncateDataByMaxsize() throws Exception {
+        when(mockMailboxManager.getAllAnnotations(inbox, mailboxSession)).thenReturn(ImmutableList.of(PRIVATE_ANNOTATION, SHARED_ANNOTATION));
+
+        processor.process(annotationRequestBuilder.maxsize(Optional.of(100)).build(), mockResponder, mockImapSession);
+
+        verify(mockStatusResponseFactory, times(1)).taggedOk(any(String.class),
+                any(ImapCommand.class),
+                humanTextCaptor.capture());
+        verify(mockResponder, times(2)).respond(captorAnnotationResponse.capture());
+        verifyNoMoreInteractions(mockResponder);
+
+        assertThat(humanTextCaptor.getAllValues()).containsOnly(HumanReadableText.COMPLETED);
+    }
+
+    @Test
+    public void processShouldGetAnnotationsAndReturnCompleteResponseWithTruncateDataByMaxsize() throws Exception {
+        when(mockMailboxManager.getAllAnnotations(inbox, mailboxSession)).thenReturn(ImmutableList.of(SHARED_ANNOTATION, PRIVATE_ANNOTATION, PRIVATE_CHILD_ANNOTATION, PRIVATE_GRANDCHILD_ANNOTATION));
+
+        processor.process(annotationRequestBuilder.maxsize(Optional.of(15)).build(), mockResponder, mockImapSession);
+
+        verify(mockStatusResponseFactory, times(1)).taggedOk(any(String.class),
+            any(ImapCommand.class),
+            humanTextCaptor.capture(),
+            any(ResponseCode.class));
+        verify(mockResponder, times(2)).respond(captorAnnotationResponse.capture());
+        verifyNoMoreInteractions(mockResponder);
+
+        AnnotationResponse resultAnnotation = captorAnnotationResponse.getAllValues().get(FIRST_ELEMENT_INDEX);
+        assertThat(resultAnnotation.getMailboxAnnotations()).contains(PRIVATE_ANNOTATION, SHARED_ANNOTATION);
+    }
+
+    @Test
+    public void processShouldGetAnnotationsAndReturnCompleteResponseDoesnotTruncateDataByMaxsizeWhenNoMoreOverSizeItem() throws Exception {
+        when(mockMailboxManager.getAllAnnotations(inbox, mailboxSession)).thenReturn(ImmutableList.of(SHARED_ANNOTATION, PRIVATE_ANNOTATION, PRIVATE_CHILD_ANNOTATION, PRIVATE_GRANDCHILD_ANNOTATION));
+
+        processor.process(annotationRequestBuilder.maxsize(Optional.of(100)).build(), mockResponder, mockImapSession);
+
+        verify(mockStatusResponseFactory, times(1)).taggedOk(any(String.class),
+            any(ImapCommand.class),
+            humanTextCaptor.capture());
+        verify(mockResponder, times(2)).respond(captorAnnotationResponse.capture());
+        verifyNoMoreInteractions(mockResponder);
+
+        AnnotationResponse resultAnnotation = captorAnnotationResponse.getAllValues().get(FIRST_ELEMENT_INDEX);
+        assertThat(resultAnnotation.getMailboxAnnotations()).contains(PRIVATE_ANNOTATION, SHARED_ANNOTATION, PRIVATE_CHILD_ANNOTATION, PRIVATE_GRANDCHILD_ANNOTATION);
+    }
+
+    @Test
+    public void processShouldGetAnnotationsByOneDepthAndReturnCompleteResponseWithTruncateDataByMaxsize() throws Exception {
+        when(mockMailboxManager.getAnnotationsByKeysWithOneDepth(inbox, mailboxSession, KEYS)).thenReturn(ImmutableList.of(PRIVATE_ANNOTATION, PRIVATE_CHILD_ANNOTATION));
+
+        processor.process(annotationRequestBuilder.maxsize(Optional.of(14)).depth(Depth.ONE).keys(KEYS).build(), mockResponder, mockImapSession);
+
+        verify(mockMailboxManager, times(1)).getAnnotationsByKeysWithOneDepth(eq(inbox), eq(mailboxSession), eq(KEYS));
+        verify(mockStatusResponseFactory, times(1)).taggedOk(any(String.class),
+            any(ImapCommand.class),
+            humanTextCaptor.capture(),
+            any(ResponseCode.class));
+        verify(mockResponder, times(2)).respond(captorAnnotationResponse.capture());
+        verifyNoMoreInteractions(mockResponder);
+
+        AnnotationResponse resultAnnotation = captorAnnotationResponse.getAllValues().get(FIRST_ELEMENT_INDEX);
+        assertThat(resultAnnotation.getMailboxAnnotations()).contains(PRIVATE_ANNOTATION);
+    }
+
+    @Test
+    public void processShouldGetAnnotationsAndReturnCompleteResponseWithTruncateDataByLessThenOrEqualMaxsize() throws Exception {
+        when(mockMailboxManager.getAllAnnotations(inbox, mailboxSession)).thenReturn(ImmutableList.of(PRIVATE_ANNOTATION, SHARED_ANNOTATION));
+
+        processor.process(annotationRequestBuilder.maxsize(Optional.of(15)).build(), mockResponder, mockImapSession);
+
+        verify(mockMailboxManager, times(1)).getAllAnnotations(eq(inbox), eq(mailboxSession));
+        verify(mockStatusResponseFactory, times(1)).taggedOk(any(String.class),
+            any(ImapCommand.class),
+            humanTextCaptor.capture());
+
+        verify(mockResponder, times(2)).respond(captorAnnotationResponse.capture());
+        verifyNoMoreInteractions(mockResponder);
+
+        AnnotationResponse resultAnnotation = captorAnnotationResponse.getAllValues().get(FIRST_ELEMENT_INDEX);
+        assertThat(resultAnnotation.getMailboxAnnotations()).contains(PRIVATE_ANNOTATION, SHARED_ANNOTATION);
+    }
+
+    @Test
+    public void processShouldGetAnnotationsByInfinityDepthAndReturnCompleteResponseWithTruncateDataByMaxsize() throws Exception {
+        when(mockMailboxManager.getAnnotationsByKeysWithAllDepth(inbox, mailboxSession, KEYS)).thenReturn(ImmutableList.of(PRIVATE_ANNOTATION, PRIVATE_CHILD_ANNOTATION, PRIVATE_GRANDCHILD_ANNOTATION));
+
+        processor.process(annotationRequestBuilder.maxsize(Optional.of(14)).depth(Depth.INFINITY).keys(KEYS).build(), mockResponder, mockImapSession);
+
+        verify(mockMailboxManager, times(1)).getAnnotationsByKeysWithAllDepth(eq(inbox), eq(mailboxSession), eq(KEYS));
+        verify(mockStatusResponseFactory, times(1)).taggedOk(any(String.class),
+            any(ImapCommand.class),
+            humanTextCaptor.capture(),
+            any(ResponseCode.class));
+        verify(mockResponder, times(2)).respond(captorAnnotationResponse.capture());
+        verifyNoMoreInteractions(mockResponder);
+
+        AnnotationResponse resultAnnotation = captorAnnotationResponse.getAllValues().get(FIRST_ELEMENT_INDEX);
+
+        assertThat(resultAnnotation.getMailboxAnnotations()).contains(PRIVATE_ANNOTATION);
+    }
+
+    @Test
+    public void processShouldGetAnnotationsByInfinityDepthAndReturnCompleteResponse() throws Exception {
+        when(mockMailboxManager.getAnnotationsByKeysWithAllDepth(inbox, mailboxSession, KEYS)).thenReturn(ImmutableList.of(PRIVATE_ANNOTATION, PRIVATE_CHILD_ANNOTATION, PRIVATE_GRANDCHILD_ANNOTATION));
+
+        processor.process(annotationRequestBuilder.depth(Depth.INFINITY).keys(KEYS).build(), mockResponder, mockImapSession);
+
+        verify(mockStatusResponseFactory, times(1)).taggedOk(any(String.class),
+            any(ImapCommand.class),
+            humanTextCaptor.capture());
+        verify(mockResponder, times(2)).respond(captorAnnotationResponse.capture());
+        verifyNoMoreInteractions(mockResponder);
+
+        AnnotationResponse resultAnnotation = captorAnnotationResponse.getAllValues().get(FIRST_ELEMENT_INDEX);
+        assertThat(resultAnnotation.getMailboxAnnotations()).contains(PRIVATE_ANNOTATION, PRIVATE_CHILD_ANNOTATION, PRIVATE_GRANDCHILD_ANNOTATION);
+    }
+
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/ced2d52f/protocols/src/site/xdoc/imap4.xml
----------------------------------------------------------------------
diff --git a/protocols/src/site/xdoc/imap4.xml b/protocols/src/site/xdoc/imap4.xml
index ee37a0a..0ec2cbd 100644
--- a/protocols/src/site/xdoc/imap4.xml
+++ b/protocols/src/site/xdoc/imap4.xml
@@ -55,8 +55,9 @@
        <li>SASL-IR (in release 0.2.1)</li>
        <li>ENABLE (in release 0.2.1)</li>
        <li>CONDSTORE (RFC 4551 http://www.ietf.org/rfc/rfc4551.txt in release 0.3)</li>
-       <li>RESYNCH (RFC 5162 http://www.ietf.org/rfc/rfc5162.txt in trunk)</li>
-       <li>MOVE (RFC 6851 https://tools.ietf.org/html/rfc6851 in trunk). This is enabled only if you use a MailboxManager exposing the Move capability</li>
+       <li>RESYNCH (RFC 5162 http://www.ietf.org/rfc/rfc5162.txt on master)</li>
+       <li>MOVE (RFC 6851 https://tools.ietf.org/html/rfc6851 on master). This is enabled only if you use a MailboxManager exposing the Move capability</li>
+       <li>METADATA Extension (RFC 5464 http://www.ietf.org/rfc/rfc5464.txt on master). This is enabled only if you use a MailboxManager exposing the Annotation capability</li>
      </ul>
      <p>We follow RFC2683 recommendations for our implementations:</p>
      <ul>


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