Skip to content

Commit

Permalink
JAMES-1818 Introduce mailboxId serializer/deserializer
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphael Ouazana committed Sep 5, 2016
1 parent 4a25501 commit 460b650
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 21 deletions.
Expand Up @@ -19,30 +19,98 @@

package org.apache.james.jmap.json;

import java.io.IOException;
import java.util.Set;

import javax.inject.Inject;

import org.apache.james.mailbox.model.MailboxId;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.google.common.collect.ImmutableSet;

public class ObjectMapperFactory {

private static final Set<Module> JACKSON_MODULES = ImmutableSet.of(new Jdk8Module(), new JavaTimeModule(), new GuavaModule());
private static final ImmutableSet.Builder<Module> JACKSON_BASE_MODULES = ImmutableSet.<Module>builder().add(new Jdk8Module(), new JavaTimeModule(), new GuavaModule());
private final Set<Module> jacksonModules;

@Inject
public ObjectMapperFactory(MailboxId.Factory mailboxIdFactory) {
SimpleModule mailboxIdModule = new SimpleModule();
mailboxIdModule.addDeserializer(MailboxId.class, new MailboxIdDeserializer(mailboxIdFactory));
mailboxIdModule.addSerializer(MailboxId.class, new MailboxIdSerializer());
mailboxIdModule.addKeyDeserializer(MailboxId.class, new MailboxIdKeyDeserializer(mailboxIdFactory));
mailboxIdModule.addKeySerializer(MailboxId.class, new MailboxIdKeySerializer());
jacksonModules = JACKSON_BASE_MODULES.add(mailboxIdModule).build();
}

public ObjectMapper forParsing() {
return new ObjectMapper()
.registerModules(JACKSON_MODULES)
.registerModules(jacksonModules)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}

public ObjectMapper forWriting() {
return new ObjectMapper()
.registerModules(JACKSON_MODULES)
.registerModules(jacksonModules)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}

public static class MailboxIdDeserializer extends JsonDeserializer<MailboxId> {
private MailboxId.Factory factory;

public MailboxIdDeserializer(MailboxId.Factory factory) {
this.factory = factory;
}

@Override
public MailboxId deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return factory.fromString(p.getValueAsString());
}
}

public static class MailboxIdSerializer extends JsonSerializer<MailboxId> {

@Override
public void serialize(MailboxId value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeString(value.serialize());
}
}

public static class MailboxIdKeyDeserializer extends KeyDeserializer {
private MailboxId.Factory factory;

public MailboxIdKeyDeserializer(MailboxId.Factory factory) {
this.factory = factory;
}

@Override
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return factory.fromString(key);
}
}

public static class MailboxIdKeySerializer extends JsonSerializer<MailboxId> {

@Override
public void serialize(MailboxId value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeFieldName(value.serialize());
}
}

}
@@ -0,0 +1,117 @@
/****************************************************************
* 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.json;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Map;

import org.apache.james.mailbox.inmemory.InMemoryId;
import org.apache.james.mailbox.model.MailboxId;
import org.junit.Before;
import org.junit.Test;

import com.google.common.collect.ImmutableMap;

public class ObjectMapperFactoryTest {

private ObjectMapperFactory testee;

@Before
public void setup() {
testee = new ObjectMapperFactory(new InMemoryId.Factory());
}

@Test
public void mailboxIdShouldBeDeserializable() throws Exception {
String json = "{ \"mailboxId\": \"123\"}";
MailboxIdTestContainer expected = new MailboxIdTestContainer(InMemoryId.of(123));
MailboxIdTestContainer actual = testee.forParsing().readValue(json, MailboxIdTestContainer.class);
assertThat(actual).isEqualToComparingFieldByField(expected);
}

@Test
public void mailboxIdShouldBeSerializable() throws Exception {
MailboxIdTestContainer container = new MailboxIdTestContainer(InMemoryId.of(123));
String expectedJson = "{\"mailboxId\":\"123\"}";
String actual = testee.forWriting().writeValueAsString(container);
assertThat(actual).isEqualTo(expectedJson);
}

@Test
public void mailboxIdShouldBeDeserializableWhenKey() throws Exception {
String json = "{ \"map\": {\"123\": \"value\"}}";
MailboxIdKeyTestContainer expected = new MailboxIdKeyTestContainer(ImmutableMap.of(InMemoryId.of(123), "value"));
MailboxIdKeyTestContainer actual = testee.forParsing().readValue(json, MailboxIdKeyTestContainer.class);
assertThat(actual).isEqualToComparingFieldByField(expected);
}

@Test
public void mailboxIdShouldBeSerializableWhenKeyWithoutToString() throws Exception {
ObjectMapperFactory testeeWithoutToString = new ObjectMapperFactory(new KeyWithoutToString.Factory());
MailboxIdKeyTestContainer container = new MailboxIdKeyTestContainer(ImmutableMap.of(new KeyWithoutToString("key"), "value"));
String expectedJson = "{\"map\":{\"key\":\"value\"}}";
String actual = testeeWithoutToString.forWriting().writeValueAsString(container);
assertThat(actual).isEqualTo(expectedJson);
}

public static class MailboxIdTestContainer {
public MailboxId mailboxId;

public MailboxIdTestContainer() {
}

public MailboxIdTestContainer(MailboxId mailboxId) {
this.mailboxId = mailboxId;
}
}

public static class MailboxIdKeyTestContainer {
public Map<MailboxId, String> map;

public MailboxIdKeyTestContainer() {
}

public MailboxIdKeyTestContainer(Map<MailboxId, String> map) {
this.map = map;
}
}

public static class KeyWithoutToString implements MailboxId {
private String value;

public KeyWithoutToString(String value) {
this.value = value;
}

@Override
public String serialize() {
return value;
}

public static class Factory implements MailboxId.Factory {

@Override
public MailboxId fromString(String serialized) {
return new KeyWithoutToString(serialized);
}

}
}
}
Expand Up @@ -30,6 +30,7 @@
import org.apache.james.jmap.methods.JmapResponseWriterImpl;
import org.apache.james.jmap.model.Message;
import org.apache.james.jmap.model.SubMessage;
import org.apache.james.mailbox.inmemory.InMemoryId;
import org.junit.Test;

import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
Expand All @@ -41,7 +42,7 @@ public class ParsingWritingObjectsTest {
public void parsingJsonShouldWorkOnSubMessage() throws Exception {
SubMessage expected = SUB_MESSAGE;

SubMessage subMessage = new ObjectMapperFactory().forParsing()
SubMessage subMessage = new ObjectMapperFactory(new InMemoryId.Factory()).forParsing()
.readValue(IOUtils.toString(ClassLoader.getSystemResource("json/subMessage.json")), SubMessage.class);

assertThat(subMessage).isEqualToComparingFieldByField(expected);
Expand All @@ -51,7 +52,7 @@ public void parsingJsonShouldWorkOnSubMessage() throws Exception {
public void writingJsonShouldWorkOnSubMessage() throws Exception {
String expected = IOUtils.toString(ClassLoader.getSystemResource("json/subMessage.json"));

String json = new ObjectMapperFactory().forWriting()
String json = new ObjectMapperFactory(new InMemoryId.Factory()).forWriting()
.writeValueAsString(SUB_MESSAGE);

assertThatJson(json)
Expand All @@ -64,7 +65,7 @@ public void writingJsonShouldWorkOnSubMessage() throws Exception {
public void parsingJsonShouldWorkOnMessage() throws Exception {
Message expected = MESSAGE;

Message message = new ObjectMapperFactory().forParsing()
Message message = new ObjectMapperFactory(new InMemoryId.Factory()).forParsing()
.readValue(IOUtils.toString(ClassLoader.getSystemResource("json/message.json")), Message.class);

assertThat(message).isEqualToComparingFieldByField(expected);
Expand All @@ -78,7 +79,7 @@ public void writingJsonShouldWorkOnMessage() throws Exception {
.addFilter(JmapResponseWriterImpl.PROPERTIES_FILTER, SimpleBeanPropertyFilter.serializeAll())
.addFilter(GetMessagesMethod.HEADERS_FILTER, SimpleBeanPropertyFilter.serializeAll());

String json = new ObjectMapperFactory().forWriting()
String json = new ObjectMapperFactory(new InMemoryId.Factory()).forWriting()
.setFilterProvider(filterProvider)
.writeValueAsString(MESSAGE);

Expand Down
Expand Up @@ -21,21 +21,28 @@

import org.apache.james.jmap.json.ObjectMapperFactory;
import org.apache.james.jmap.model.ProtocolRequest;
import org.apache.james.mailbox.inmemory.InMemoryId;
import org.junit.Before;
import org.junit.Test;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class JmapRequestParserImplTest {
private JmapRequestParserImpl jmapRequestParserImpl;

@Before
public void setup() {
jmapRequestParserImpl = new JmapRequestParserImpl(new ObjectMapperFactory(new InMemoryId.Factory()));
}

@Test(expected=IllegalArgumentException.class)
public void extractJmapRequestShouldThrowWhenNullRequestClass() throws Exception {
JsonNode[] nodes = new JsonNode[] { new ObjectNode(new JsonNodeFactory(false)).textNode("unknwonMethod"),
new ObjectNode(new JsonNodeFactory(false)).putObject("{\"id\": \"id\"}"),
new ObjectNode(new JsonNodeFactory(false)).textNode("#1")} ;

JmapRequestParserImpl jmapRequestParserImpl = new JmapRequestParserImpl(new ObjectMapperFactory());
jmapRequestParserImpl.extractJmapRequest(ProtocolRequest.deserialize(nodes), null);
}

Expand All @@ -47,7 +54,6 @@ public void extractJmapRequestShouldNotThrowWhenJsonContainsUnknownProperty() th
parameters,
new ObjectNode(new JsonNodeFactory(false)).textNode("#1")} ;

JmapRequestParserImpl jmapRequestParserImpl = new JmapRequestParserImpl(new ObjectMapperFactory());
jmapRequestParserImpl.extractJmapRequest(ProtocolRequest.deserialize(nodes), RequestClass.class);
}

Expand All @@ -58,7 +64,6 @@ public void extractJmapRequestShouldNotThrowWhenPropertyMissingInJson() throws E
parameters,
new ObjectNode(new JsonNodeFactory(false)).textNode("#1")} ;

JmapRequestParserImpl jmapRequestParserImpl = new JmapRequestParserImpl(new ObjectMapperFactory());
jmapRequestParserImpl.extractJmapRequest(ProtocolRequest.deserialize(nodes), RequestClass.class);
}

Expand Down
Expand Up @@ -32,6 +32,8 @@
import org.apache.james.jmap.model.Property;
import org.apache.james.jmap.model.ProtocolRequest;
import org.apache.james.jmap.model.ProtocolResponse;
import org.apache.james.mailbox.inmemory.InMemoryId;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

Expand All @@ -44,6 +46,12 @@
import com.google.common.collect.Iterables;

public class JmapResponseWriterImplTest {
private JmapResponseWriterImpl jmapResponseWriterImpl;

@Before
public void setup() {
jmapResponseWriterImpl = new JmapResponseWriterImpl(new ObjectMapperFactory(new InMemoryId.Factory()));
}

@Ignore
@Test(expected=IllegalStateException.class)
Expand All @@ -52,7 +60,6 @@ public void formatMethodResponseShouldWorkWhenNullJmapResponse() {
String expectedClientId = "#1";
String expectedId = "myId";

JmapResponseWriterImpl jmapResponseWriterImpl = new JmapResponseWriterImpl(new ObjectMapperFactory());
Stream<ProtocolResponse> response = jmapResponseWriterImpl.formatMethodResponse(Stream.of(JmapResponse
.builder()
.clientId(ClientId.of(expectedClientId))
Expand All @@ -73,7 +80,6 @@ public void formatMethodResponseShouldWork() {
ResponseClass responseClass = new ResponseClass();
responseClass.id = expectedId;

JmapResponseWriterImpl jmapResponseWriterImpl = new JmapResponseWriterImpl(new ObjectMapperFactory());
List<ProtocolResponse> response = jmapResponseWriterImpl.formatMethodResponse(
Stream.of(JmapResponse
.builder()
Expand Down Expand Up @@ -101,7 +107,6 @@ public void formatMethodResponseShouldFilterFieldsWhenProperties() {
responseClass.list = ImmutableList.of(new ObjectResponseClass.Foo("id", "name"));
Property property = () -> "id";

JmapResponseWriterImpl jmapResponseWriterImpl = new JmapResponseWriterImpl(new ObjectMapperFactory());
List<ProtocolResponse> response = jmapResponseWriterImpl.formatMethodResponse(
Stream.of(JmapResponse
.builder()
Expand All @@ -126,7 +131,6 @@ public void formatMethodResponseShouldNotFilterFieldsWhenSecondCallWithoutProper
responseClass.list = ImmutableList.of(new ObjectResponseClass.Foo("id", "name"));
Property property = () -> "id";

JmapResponseWriterImpl jmapResponseWriterImpl = new JmapResponseWriterImpl(new ObjectMapperFactory());
@SuppressWarnings("unused")
Stream<ProtocolResponse> ignoredResponse = jmapResponseWriterImpl.formatMethodResponse(
Stream.of(JmapResponse
Expand Down Expand Up @@ -159,8 +163,6 @@ public void formatMethodResponseShouldFilterRightFieldsForEachResponse() {
Property idProperty = () -> "id";
Property nameProperty = () -> "name";

JmapResponseWriterImpl jmapResponseWriterImpl = new JmapResponseWriterImpl(new ObjectMapperFactory());

List<ProtocolResponse> response = jmapResponseWriterImpl.formatMethodResponse(
Stream.of(JmapResponse
.builder()
Expand Down Expand Up @@ -210,7 +212,6 @@ public void formatErrorResponseShouldWork() {
parameters,
new ObjectNode(new JsonNodeFactory(false)).textNode(expectedClientId)} ;

JmapResponseWriterImpl jmapResponseWriterImpl = new JmapResponseWriterImpl(new ObjectMapperFactory());
List<ProtocolResponse> response = jmapResponseWriterImpl.formatMethodResponse(
Stream.of(JmapResponse
.builder()
Expand All @@ -234,7 +235,6 @@ public void formatErrorResponseShouldWorkWithTypeAndDescription() {
parameters,
new ObjectNode(new JsonNodeFactory(false)).textNode(expectedClientId)} ;

JmapResponseWriterImpl jmapResponseWriterImpl = new JmapResponseWriterImpl(new ObjectMapperFactory());
List<ProtocolResponse> response = jmapResponseWriterImpl.formatMethodResponse(
Stream.of(JmapResponse
.builder()
Expand Down

0 comments on commit 460b650

Please sign in to comment.