blob: 0736dbee3ba9c82cf1c26fb8cdae17984a0e65d8 [file] [log] [blame]
/****************************************************************
* 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.mailet;
import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.ZonedDateTime;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.james.mailbox.model.MessageIdDto;
import org.apache.james.util.streams.Iterators;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.DoubleNode;
import com.fasterxml.jackson.databind.node.FloatNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.LongNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/**
* Controlled Json serialization/deserialization.
*
* @since Mailet API v3.2
*/
@SuppressWarnings("EqualsHashCode")
public interface Serializer<T> {
Optional<JsonNode> serialize(T object);
Optional<T> deserialize(JsonNode json);
String getName();
T duplicate(T value);
class Registry {
private static ImmutableMap<String, Serializer<?>> serializers;
static {
serializers = Stream
.<Serializer<?>>of(
BOOLEAN_SERIALIZER,
STRING_SERIALIZER,
INT_SERIALIZER,
LONG_SERIALIZER,
FLOAT_SERIALIZER,
DOUBLE_SERIALIZER,
DATE_SERIALIZER,
MESSAGE_ID_DTO_SERIALIZER,
BYTES_SERIALIZER,
new Serializer.ArbitrarySerializableSerializer<>(),
URL_SERIALIZER,
new CollectionSerializer<>(),
new MapSerializer<>(),
// To be dropped in 3.9.0
new FSTSerializer(),
new OptionalSerializer<>())
.collect(ImmutableMap.toImmutableMap(Serializer::getName, Function.identity()));
}
static Optional<Serializer<?>> find(String name) {
return Optional.ofNullable(serializers.get(name));
}
}
class BooleanSerializer implements Serializer<Boolean> {
@Override
public Optional<JsonNode> serialize(Boolean object) {
return Optional.of(BooleanNode.valueOf(object));
}
@Override
public Optional<Boolean> deserialize(JsonNode json) {
if (json instanceof BooleanNode) {
return Optional.of(json.asBoolean());
} else {
return Optional.empty();
}
}
@Override
public Boolean duplicate(Boolean value) {
return value;
}
@Override
public String getName() {
return "BooleanSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
Serializer<Boolean> BOOLEAN_SERIALIZER = new BooleanSerializer();
class StringSerializer implements Serializer<String> {
@Override
public Optional<JsonNode> serialize(String object) {
return Optional.of(TextNode.valueOf(object));
}
@Override
public Optional<String> deserialize(JsonNode json) {
if (json instanceof TextNode) {
return Optional.of(json.asText());
} else {
return Optional.empty();
}
}
@Override
public String duplicate(String value) {
return value;
}
@Override
public String getName() {
return "StringSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
Serializer<String> STRING_SERIALIZER = new StringSerializer();
class IntSerializer implements Serializer<Integer> {
@Override
public Optional<JsonNode> serialize(Integer object) {
return Optional.of(IntNode.valueOf(object));
}
@Override
public Optional<Integer> deserialize(JsonNode json) {
if (json instanceof IntNode) {
return Optional.of(json.asInt());
} else {
return Optional.empty();
}
}
@Override
public Integer duplicate(Integer value) {
return value;
}
@Override
public String getName() {
return "IntSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
Serializer<Integer> INT_SERIALIZER = new IntSerializer();
class LongSerializer implements Serializer<Long> {
@Override
public Optional<JsonNode> serialize(Long object) {
return Optional.of(LongNode.valueOf(object));
}
@Override
public Optional<Long> deserialize(JsonNode json) {
if (json instanceof LongNode) {
return Optional.of(json.asLong());
} else if (json instanceof IntNode) {
return Optional.of(Long.valueOf(json.asInt()));
} else {
return Optional.empty();
}
}
@Override
public Long duplicate(Long value) {
return value;
}
@Override
public String getName() {
return "LongSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
Serializer<Long> LONG_SERIALIZER = new LongSerializer();
class FloatSerializer implements Serializer<Float> {
@Override
public Optional<JsonNode> serialize(Float object) {
return Optional.of(FloatNode.valueOf(object));
}
@Override
public Optional<Float> deserialize(JsonNode json) {
if (json instanceof FloatNode) {
return Optional.of(json.floatValue());
} else if (json instanceof DoubleNode) {
return Optional.of(json.floatValue());
} else {
return Optional.empty();
}
}
@Override
public Float duplicate(Float value) {
return value;
}
@Override
public String getName() {
return "FloatSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
Serializer<Float> FLOAT_SERIALIZER = new FloatSerializer();
class DoubleSerializer implements Serializer<Double> {
@Override
public Optional<JsonNode> serialize(Double object) {
return Optional.of(DoubleNode.valueOf(object));
}
@Override
public Optional<Double> deserialize(JsonNode json) {
if (json instanceof DoubleNode || json instanceof FloatNode) {
return Optional.of(json.asDouble());
} else {
return Optional.empty();
}
}
@Override
public Double duplicate(Double value) {
return value;
}
@Override
public String getName() {
return "DoubleSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
Serializer<Double> DOUBLE_SERIALIZER = new DoubleSerializer();
class DateSerializer implements Serializer<ZonedDateTime> {
@Override
public Optional<JsonNode> serialize(ZonedDateTime object) {
String serialized = object.format(ISO_DATE_TIME);
return Optional.of(TextNode.valueOf(serialized));
}
@Override
public Optional<ZonedDateTime> deserialize(JsonNode json) {
if (json instanceof TextNode) {
String serialized = json.asText();
return Optional.of(ZonedDateTime.parse(serialized, ISO_DATE_TIME));
} else {
return Optional.empty();
}
}
@Override
public ZonedDateTime duplicate(ZonedDateTime value) {
return ZonedDateTime.ofInstant(value.toInstant(), value.getZone());
}
@Override
public String getName() {
return "DateSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
Serializer<ZonedDateTime> DATE_SERIALIZER = new DateSerializer();
class MessageIdDtoSerializer implements Serializer<MessageIdDto> {
@Override
public Optional<JsonNode> serialize(MessageIdDto serializable) {
return STRING_SERIALIZER
.serialize(serializable.asString());
}
@Override
public Optional<MessageIdDto> deserialize(JsonNode json) {
return STRING_SERIALIZER
.deserialize(json)
.map(MessageIdDto::new);
}
@Override
public MessageIdDto duplicate(MessageIdDto value) {
return value;
}
@Override
public String getName() {
return "MessageIdDtoSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
Serializer<MessageIdDto> MESSAGE_ID_DTO_SERIALIZER = new MessageIdDtoSerializer();
class ArbitrarySerializableSerializer<T extends ArbitrarySerializable<T>> implements Serializer<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(ArbitrarySerializableSerializer.class);
@Override
public Optional<JsonNode> serialize(T serializable) {
ArbitrarySerializable.Serializable<T> serialized = serializable.serialize();
ObjectNode serializedJson = JsonNodeFactory.instance.objectNode();
serializedJson.put("deserializer", serialized.getDeserializer().getName());
serializedJson.replace("value", serialized.getValue().toJson().get());
return Optional.of(serializedJson);
}
@Override
public Optional<T> deserialize(JsonNode json) {
return Optional.of(json)
.filter(ObjectNode.class::isInstance)
.map(ObjectNode.class::cast)
.flatMap(this::instantiate);
}
public Optional<T> instantiate(ObjectNode fields) {
return Optional.ofNullable(fields.get("deserializer"))
.flatMap(serializer ->
Optional.ofNullable(fields.get("value"))
.flatMap(value -> deserialize(serializer.asText(), AttributeValue.fromJson(value))));
}
@SuppressWarnings("unchecked")
private Optional<T> deserialize(String serializer, AttributeValue<?> value) {
try {
Class<?> deserializerClass = Class.forName(serializer);
if (ArbitrarySerializable.Deserializer.class.isAssignableFrom(deserializerClass)) {
ArbitrarySerializable.Deserializer<T> deserializer = (ArbitrarySerializable.Deserializer<T>) deserializerClass.getDeclaredConstructor().newInstance();
return deserializer.deserialize(new ArbitrarySerializable.Serializable<>(value, (Class<ArbitrarySerializable.Deserializer<T>>) deserializerClass));
}
} catch (Exception e) {
LOGGER.error("Error while deserializing using serializer {} and value {}", serializer, value, e);
}
return Optional.empty();
}
@Override
public String getName() {
return "ArbitrarySerializableSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
@Override
public T duplicate(T value) {
return deserialize(serialize(value).get()).get();
}
}
class UrlSerializer implements Serializer<URL> {
@Override
public Optional<JsonNode> serialize(URL object) {
return STRING_SERIALIZER.serialize(object.toString());
}
@Override
public Optional<URL> deserialize(JsonNode json) {
return STRING_SERIALIZER.deserialize(json).flatMap(url -> {
try {
return Optional.of(new URI(url).toURL());
} catch (MalformedURLException | URISyntaxException e) {
return Optional.empty();
}
});
}
@Override
public String getName() {
return "UrlSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
@Override
public URL duplicate(URL value) {
return value;
}
}
Serializer<URL> URL_SERIALIZER = new UrlSerializer();
class CollectionSerializer<U> implements Serializer<Collection<AttributeValue<U>>> {
@Override
public Optional<JsonNode> serialize(Collection<AttributeValue<U>> object) {
List<JsonNode> jsons = object.stream()
.map(AttributeValue::toJson)
.flatMap(Optional::stream)
.collect(ImmutableList.toImmutableList());
return Optional.of(new ArrayNode(JsonNodeFactory.instance, jsons));
}
@SuppressWarnings("unchecked")
@Override
public Optional<Collection<AttributeValue<U>>> deserialize(JsonNode json) {
if (json instanceof ArrayNode) {
return Optional.of(Iterators.toStream(json.elements())
.map(value -> (AttributeValue<U>) AttributeValue.fromJson(value))
.collect(ImmutableList.toImmutableList()));
} else {
return Optional.empty();
}
}
@Override
public Collection<AttributeValue<U>> duplicate(Collection<AttributeValue<U>> value) {
return value.stream()
.map(AttributeValue::duplicate)
.collect(ImmutableList.toImmutableList());
}
@Override
public String getName() {
return "CollectionSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
class MapSerializer<U> implements Serializer<Map<String, AttributeValue<U>>> {
@Override
public Optional<JsonNode> serialize(Map<String, AttributeValue<U>> object) {
Map<String, JsonNode> jsonMap = object.entrySet().stream()
.flatMap(entry -> entry.getValue().toJson().map(value -> Pair.of(entry.getKey(), value)).stream())
.collect(ImmutableMap.toImmutableMap(Pair::getKey, Pair::getValue));
return Optional.of(new ObjectNode(JsonNodeFactory.instance, jsonMap));
}
@SuppressWarnings("unchecked")
@Override
public Optional<Map<String, AttributeValue<U>>> deserialize(JsonNode json) {
if (json instanceof ObjectNode) {
return Optional.of(Iterators.toStream(json.fields())
.collect(ImmutableMap.toImmutableMap(
Map.Entry::getKey,
entry -> (AttributeValue<U>) AttributeValue.fromJson(entry.getValue())
)));
} else {
return Optional.empty();
}
}
@Override
public Map<String, AttributeValue<U>> duplicate(Map<String, AttributeValue<U>> value) {
return value.entrySet()
.stream()
.map(entry -> Pair.of(entry.getKey(), entry.getValue().duplicate()))
.collect(ImmutableMap.toImmutableMap(Pair::getKey, Pair::getValue));
}
@Override
public String getName() {
return "MapSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
class OptionalSerializer<U> implements Serializer<Optional<AttributeValue<U>>> {
@Override
public Optional<JsonNode> serialize(Optional<AttributeValue<U>> object) {
return object.map(AttributeValue::toJson)
.orElse(Optional.of(NullNode.getInstance()));
}
@SuppressWarnings("unchecked")
@Override
public Optional<Optional<AttributeValue<U>>> deserialize(JsonNode json) {
if (json instanceof ObjectNode) {
AttributeValue<U> value = (AttributeValue<U>) AttributeValue.fromJson(json);
return Optional.of(Optional.of(value));
} else if (json instanceof NullNode) {
return Optional.of(Optional.empty());
} else {
return Optional.empty();
}
}
@Override
public Optional<AttributeValue<U>> duplicate(Optional<AttributeValue<U>> value) {
return value.map(AttributeValue::duplicate);
}
@Override
public String getName() {
return "OptionalSerializer";
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
class FSTSerializer implements Serializer<Serializable> {
@Override
public Optional<JsonNode> serialize(Serializable object) {
return Optional.empty();
}
@Override
public Optional<Serializable> deserialize(JsonNode json) {
return Optional.empty();
}
@Override
public String getName() {
return "NoSerializer";
}
@Override
public Serializable duplicate(Serializable value) {
throw new NotImplementedException();
}
}
Serializer<byte[]> BYTES_SERIALIZER = new BytesSerializer();
class BytesSerializer implements Serializer<byte[]> {
@Override
public Optional<JsonNode> serialize(byte[] object) {
return STRING_SERIALIZER.serialize(Base64.getEncoder().encodeToString(object));
}
@Override
public Optional<byte[]> deserialize(JsonNode json) {
return STRING_SERIALIZER.deserialize(json).map(Base64.getDecoder()::decode);
}
@Override
public String getName() {
return "BytesSerializer";
}
@Override
public byte[] duplicate(byte[] value) {
// Assume byte arrays never to be mutated
return value;
}
@Override
public boolean equals(Object other) {
return this.getClass() == other.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
}
class NoSerializer implements Serializer<Object> {
@Override
public Optional<JsonNode> serialize(Object object) {
return Optional.empty();
}
@Override
public Optional<Object> deserialize(JsonNode json) {
return Optional.empty();
}
@Override
public String getName() {
return "NoSerializer";
}
@Override
public Object duplicate(Object value) {
return value;
}
}
}