com.addthis.codec.jackson.Jackson.java Source code

Java tutorial

Introduction

Here is the source code for com.addthis.codec.jackson.Jackson.java

Source

/*
 * 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 com.addthis.codec.jackson;

import javax.validation.Validator;

import java.io.IOException;

import java.util.Iterator;
import java.util.List;

import com.addthis.codec.plugins.PluginRegistry;
import com.addthis.codec.utils.ExecutorsModule;

import com.google.common.annotations.Beta;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.PropertyBindingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValue;

import static com.fasterxml.jackson.databind.DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY;
import static com.fasterxml.jackson.databind.MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS;
import static com.fasterxml.jackson.databind.MapperFeature.INFER_PROPERTY_MUTATORS;
import static com.fasterxml.jackson.databind.MapperFeature.USE_GETTERS_AS_SETTERS;

/**
 * Utility methods for interacting with the jackson library and creating more customized
 * object mappers.
 */
@Beta
public final class Jackson {
    private Jackson() {
    }

    public static final ObjectMapper SIMPLE_MAPPER = new ObjectMapper();
    public static final JavaType NODE_TYPE = SIMPLE_MAPPER.constructType(JsonNode.class);

    public static ObjectMapper defaultMapper() {
        return DefaultCodecJackson.DEFAULT_MAPPER;
    }

    public static CodecJackson defaultCodec() {
        return DefaultCodecJackson.DEFAULT_CODEC;
    }

    public static Validator defaultValidator() {
        return DefaultCodecJackson.DEFAULT_VALIDATOR;
    }

    public static ObjectMapper newObjectMapper(PluginRegistry pluginRegistry) {
        CodecModule codecModule = new CodecModule(pluginRegistry);
        Config globalConfig = pluginRegistry.config();
        ObjectMapper objectMapper = new ObjectMapper();
        toggleObjectMapperOptions(objectMapper);
        objectMapper.registerModule(codecModule);
        registerExtraModules(objectMapper);
        allowCommentsAndUnquotedFields(objectMapper);
        objectMapper.setInjectableValues(new BasicInjectableValues());
        return objectMapper;
    }

    public static ObjectMapper registerExtraModules(ObjectMapper objectMapper) {
        objectMapper.registerModule(new GuavaModule());
        objectMapper.registerModule(new Jdk8Module());
        // jsr310 is basically just the jdk 8 date/time classes split into its own module
        objectMapper.registerModule(new JSR310Module());
        objectMapper.registerModule(new JodaModule());
        objectMapper.registerModule(new ExecutorsModule());
        return objectMapper;
    }

    public static ObjectMapper allowCommentsAndUnquotedFields(ObjectMapper objectMapper) {
        objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
        objectMapper.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);
        objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        return objectMapper;
    }

    public static ObjectMapper toggleObjectMapperOptions(ObjectMapper objectMapper) {
        // potentially useful features, but disabled by default to maintain existing behavior

        // ignore final fields
        objectMapper.disable(ALLOW_FINAL_FIELDS_AS_MUTATORS);
        // do not try to modify existing containers
        objectMapper.disable(USE_GETTERS_AS_SETTERS);
        // public getters do not automaticaly imply codec should try to write to it
        objectMapper.disable(INFER_PROPERTY_MUTATORS);

        // more aggressive failure detection
        objectMapper.enable(FAIL_ON_READING_DUP_TREE_KEY);

        // essentially auto-collection everywhere, but that seems fine and this is easy
        objectMapper.enable(ACCEPT_SINGLE_VALUE_AS_ARRAY);

        // don't write out null fields
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return objectMapper;
    }

    public static JsonNode configConverter(ConfigValue source) {
        return SIMPLE_MAPPER.convertValue(source.unwrapped(), NODE_TYPE);
    }

    public static ObjectNode configConverter(ConfigObject source) {
        return SIMPLE_MAPPER.convertValue(source.unwrapped(), NODE_TYPE);
    }

    public static void merge(ObjectNode primary, ObjectNode backup) {
        Iterator<String> fieldNames = backup.fieldNames();
        while (fieldNames.hasNext()) {
            String fieldName = fieldNames.next();
            JsonNode primaryValue = primary.get(fieldName);
            if (primaryValue == null) {
                JsonNode backupValue = backup.get(fieldName).deepCopy();
                primary.set(fieldName, backupValue);
            } else if (primaryValue.isObject()) {
                JsonNode backupValue = backup.get(fieldName);
                if (backupValue.isObject()) {
                    merge((ObjectNode) primaryValue, (ObjectNode) backupValue.deepCopy());
                }
            }
        }
    }

    private static final Splitter dotSplitter = Splitter.on('.');

    public static void setAt(ObjectNode root, JsonNode value, String path) {
        if (path.indexOf('.') >= 0) {
            Iterator<String> pathIterator = dotSplitter.split(path).iterator();
            while (pathIterator.hasNext()) {
                String nextPart = pathIterator.next();
                if (pathIterator.hasNext()) {
                    root = root.with(nextPart);
                } else {
                    root.set(nextPart, value);
                }
            }
        } else {
            root.set(path, value);
        }
    }

    public static IOException maybeUnwrapPath(String pathToSkip, IOException cause) {
        if ((pathToSkip != null) && (cause instanceof JsonMappingException)) {
            JsonMappingException mappingException = (JsonMappingException) cause;
            List<JsonMappingException.Reference> paths = mappingException.getPath();
            if (!paths.isEmpty()) {
                Iterator<String> pathIterator = dotSplitter.split(pathToSkip).iterator();
                Iterator<JsonMappingException.Reference> refIterator = paths.iterator();
                while (pathIterator.hasNext()) {
                    String pathToSkipPart = pathIterator.next();
                    if (!refIterator.hasNext()) {
                        return cause;
                    }
                    String nextRefField = refIterator.next().getFieldName();
                    if (!pathToSkipPart.equals(nextRefField)) {
                        return cause;
                    }
                }
                JsonMappingException unwrapped = new JsonMappingException(rootMessage(mappingException),
                        mappingException.getLocation(), mappingException.getCause());
                if (refIterator.hasNext()) {
                    List<JsonMappingException.Reference> remainingRefs = Lists.newArrayList(refIterator);
                    for (JsonMappingException.Reference reference : Lists.reverse(remainingRefs)) {
                        unwrapped.prependPath(reference);
                    }
                }
                return unwrapped;
            }
        }
        return cause;
    }

    public static boolean isRealLocation(JsonLocation jsonLocation) {
        return (jsonLocation != null) && (jsonLocation != JsonLocation.NA);
    }

    public static JsonMappingException maybeImproveLocation(JsonLocation wrapLoc, JsonMappingException cause) {
        JsonLocation exLoc = cause.getLocation();
        if (isRealLocation(wrapLoc) && !isRealLocation(exLoc)) {
            if (wrapLoc.getSourceRef() instanceof ConfigValue) {
                ConfigValue locRef = (ConfigValue) wrapLoc.getSourceRef();
                List<JsonMappingException.Reference> paths = cause.getPath();
                for (JsonMappingException.Reference path : paths) {
                    if (locRef instanceof ConfigObject) {
                        String fieldName = path.getFieldName();
                        ConfigObject locRefObject = (ConfigObject) locRef;
                        if (locRefObject.containsKey(fieldName)) {
                            locRef = locRefObject.get(fieldName);
                        } else {
                            break;
                        }
                    } else if (locRef instanceof ConfigList) {
                        int fieldIndex = path.getIndex();
                        ConfigList locRefList = (ConfigList) locRef;
                        if ((fieldIndex >= 0) && (locRefList.size() > fieldIndex)) {
                            locRef = locRefList.get(fieldIndex);
                        } else {
                            break;
                        }
                    } else {
                        break;
                    }
                }
                if (locRef != wrapLoc.getSourceRef()) {
                    wrapLoc = fromConfigValue(locRef);
                }
            }
            List<JsonMappingException.Reference> paths = Lists.reverse(cause.getPath());
            if (!paths.isEmpty()) {
                JsonMappingException withLoc = new JsonMappingException(rootMessage(cause), wrapLoc, cause);
                for (JsonMappingException.Reference path : paths) {
                    withLoc.prependPath(path);
                }
                return withLoc;
            } else {
                return new JsonMappingException(rootMessage(cause), wrapLoc, cause);
            }
        }
        return cause;
    }

    public static String rootMessage(JsonMappingException ex) {
        String rootMessage = ex.getOriginalMessage();
        if (ex instanceof PropertyBindingException) {
            String suffix = ((PropertyBindingException) ex).getMessageSuffix();
            if (rootMessage == null) {
                return suffix;
            } else if (suffix != null) {
                return rootMessage + suffix;
            }
        }
        return rootMessage;
    }

    public static JsonLocation fromConfigValue(ConfigValue configValue) {
        ConfigOrigin configOrigin = configValue.origin();
        return new JsonLocation(configValue, -1, configOrigin.lineNumber(), -1);
    }

    public static JsonNode pathAt(ObjectNode root, String path) {
        if (path.indexOf('.') >= 0) {
            JsonNode returnNode = root;
            for (String nextPart : dotSplitter.split(path)) {
                returnNode = returnNode.path(nextPart);
            }
            return returnNode;
        } else {
            return root.path(path);
        }
    }
}