/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed 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.glowroot.common.util;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map.Entry;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.PrettyPrinter;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ContainerNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.std.NonTypedScalarSerializerBase;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.google.common.base.StandardSystemProperty;
public class ObjectMappers {
public static final String NEWLINE;
static {
String newline = StandardSystemProperty.LINE_SEPARATOR.value();
if (newline == null) {
NEWLINE = "\n";
} else {
NEWLINE = newline;
}
}
private ObjectMappers() {}
public static ObjectMapper create(Module... extraModules) {
SimpleModule module = new SimpleModule();
module.addSerializer(boolean.class, new BooleanSerializer(Boolean.class));
module.addSerializer(Enum.class, new EnumSerializer(Enum.class));
module.setDeserializerModifier(new EnumDeserializerModifier());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
mapper.registerModule(new GuavaModule());
for (Module extraModule : extraModules) {
mapper.registerModule(extraModule);
}
mapper.setSerializationInclusion(Include.NON_ABSENT);
return mapper;
}
public static PrettyPrinter getPrettyPrinter() {
CustomPrettyPrinter prettyPrinter = new CustomPrettyPrinter();
prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
return prettyPrinter;
}
public static void stripEmptyContainerNodes(ObjectNode objectNode) {
Iterator<Entry<String, JsonNode>> i = objectNode.fields();
while (i.hasNext()) {
Entry<String, JsonNode> entry = i.next();
JsonNode value = entry.getValue();
if (value instanceof ContainerNode && ((ContainerNode<?>) value).size() == 0) {
// remove empty nodes, e.g. unused "smtp" and "alerts" nodes
i.remove();
}
}
}
// com.fasterxml.jackson.databind.ser.std.BooleanSerializer is final so cannot subclass
// this is the same, plus implements isEmpty() to not write Boolean.FALSE
@SuppressWarnings("serial")
private static class BooleanSerializer extends NonTypedScalarSerializerBase<Boolean> {
private BooleanSerializer(Class<Boolean> t) {
super(t);
}
@Override
public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeBoolean(value.booleanValue());
}
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
return createSchemaNode("boolean", true);
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException {
if (visitor != null) {
visitor.expectBooleanFormat(typeHint);
}
}
@Override
public boolean isEmpty(SerializerProvider provider, Boolean value) {
return value == null || !value;
}
}
@SuppressWarnings({"rawtypes", "serial"})
private static class EnumSerializer extends StdSerializer<Enum> {
private EnumSerializer(Class<Enum> t) {
super(t);
}
@Override
public void serialize(Enum value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeString(value.name().replace('_', '-').toLowerCase(Locale.ENGLISH));
}
}
private static class EnumDeserializerModifier extends BeanDeserializerModifier {
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public JsonDeserializer<Enum> modifyEnumDeserializer(DeserializationConfig config,
final JavaType type, BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
return new JsonDeserializer<Enum>() {
@Override
public Enum<?> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
Class<? extends Enum> rawClass = (Class<Enum>) type.getRawClass();
return Enum.valueOf(rawClass,
jp.getValueAsString().replace('-', '_').toUpperCase(Locale.ENGLISH));
}
};
}
}
@SuppressWarnings("serial")
private static class CustomPrettyPrinter extends DefaultPrettyPrinter {
@Override
public void writeObjectFieldValueSeparator(JsonGenerator jg) throws IOException {
jg.writeRaw(": ");
}
}
}