org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService.java

Source

/*
 * Copyright 2013 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.springframework.data.couchbase.core.convert.translation;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.CouchbaseList;
import org.springframework.data.couchbase.core.mapping.CouchbaseStorable;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.model.SimpleTypeHolder;

import java.io.*;
import java.util.Map;

/**
 * A Jackson JSON Translator that implements the {@link TranslationService} contract.
 *
 * @author Michael Nitschinger
 */
public class JacksonTranslationService implements TranslationService, InitializingBean {

    /**
     * Jackson Object Mapper;
     */
    private ObjectMapper objectMapper;

    /**
     * Type holder to help easily identify simple types.
     */
    private SimpleTypeHolder simpleTypeHolder = new SimpleTypeHolder();

    /**
     * JSON factory for Jackson.
     */
    private JsonFactory factory = new JsonFactory();

    /**
     * Encode a {@link CouchbaseStorable} to a JSON string.
     *
     * @param source the source document to encode.
     *
     * @return the encoded JSON String.
     */
    @Override
    public final Object encode(final CouchbaseStorable source) {
        Writer writer = new StringWriter();

        try {
            JsonGenerator generator = factory.createGenerator(writer);
            encodeRecursive(source, generator);
            generator.close();
            writer.close();
        } catch (IOException ex) {
            throw new RuntimeException("Could not encode JSON", ex);
        }

        return writer.toString();
    }

    /**
     * Recursively iterates through the sources and adds it to the JSON generator.
     *
     * @param source the source document
     * @param generator the JSON generator.
     *
     * @throws IOException
     */
    private void encodeRecursive(final CouchbaseStorable source, final JsonGenerator generator) throws IOException {
        generator.writeStartObject();

        for (Map.Entry<String, Object> entry : ((CouchbaseDocument) source).export().entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            generator.writeFieldName(key);
            if (value instanceof CouchbaseDocument) {
                encodeRecursive((CouchbaseDocument) value, generator);
                continue;
            }

            final Class<?> clazz = value.getClass();

            if (simpleTypeHolder.isSimpleType(clazz) && !isEnumOrClass(clazz)) {
                generator.writeObject(value);
            } else {
                objectMapper.writeValue(generator, value);
            }

        }

        generator.writeEndObject();
    }

    private boolean isEnumOrClass(final Class<?> clazz) {
        return Enum.class.isAssignableFrom(clazz) || Class.class.isAssignableFrom(clazz);
    }

    /**
     * Decode a JSON string into the {@link CouchbaseStorable} structure.
     *
     * @param source the source formatted document.
     * @param target the target of the populated data.
     *
     * @return the decoded structure.
     */
    @Override
    public final CouchbaseStorable decode(final Object source, final CouchbaseStorable target) {
        try {
            JsonParser parser = factory.createParser((String) source);
            while (parser.nextToken() != null) {
                JsonToken currentToken = parser.getCurrentToken();

                if (currentToken == JsonToken.START_OBJECT) {
                    return decodeObject(parser, (CouchbaseDocument) target);
                } else if (currentToken == JsonToken.START_ARRAY) {
                    return decodeArray(parser, new CouchbaseList());
                } else {
                    throw new MappingException("JSON to decode needs to start as array or object!");
                }
            }
            parser.close();
        } catch (IOException ex) {
            throw new RuntimeException("Could not decode JSON", ex);
        }
        return target;
    }

    /**
     * Helper method to decode an object recursively.
     *
     * @param parser the JSON parser with the content.
     * @param target the target where the content should be stored.
     *
     * @throws IOException
     * @returns the decoded object.
     */
    private CouchbaseDocument decodeObject(final JsonParser parser, final CouchbaseDocument target)
            throws IOException {
        JsonToken currentToken = parser.nextToken();

        String fieldName = "";
        while (currentToken != null && currentToken != JsonToken.END_OBJECT) {
            if (currentToken == JsonToken.START_OBJECT) {
                target.put(fieldName, decodeObject(parser, new CouchbaseDocument()));
            } else if (currentToken == JsonToken.START_ARRAY) {
                target.put(fieldName, decodeArray(parser, new CouchbaseList()));
            } else if (currentToken == JsonToken.FIELD_NAME) {
                fieldName = parser.getCurrentName();
            } else {
                target.put(fieldName, decodePrimitive(currentToken, parser));
            }

            currentToken = parser.nextToken();
        }

        return target;
    }

    /**
     * Helper method to decode an array recusrively.
     *
     * @param parser the JSON parser with the content.
     * @param target the target where the content should be stored.
     *
     * @throws IOException
     * @returns the decoded list.
     */
    private CouchbaseList decodeArray(final JsonParser parser, final CouchbaseList target) throws IOException {
        JsonToken currentToken = parser.nextToken();

        while (currentToken != null && currentToken != JsonToken.END_ARRAY) {
            if (currentToken == JsonToken.START_OBJECT) {
                target.put(decodeObject(parser, new CouchbaseDocument()));
            } else if (currentToken == JsonToken.START_ARRAY) {
                target.put(decodeArray(parser, new CouchbaseList()));
            } else {
                target.put(decodePrimitive(currentToken, parser));
            }

            currentToken = parser.nextToken();
        }

        return target;
    }

    /**
     * Helper method to decode and assign a primitive.
     *
     * @param token the type of token.
     * @param parser the parser with the content.
     *
     * @return the decoded primitve.
     *
     * @throws IOException
     */
    private Object decodePrimitive(final JsonToken token, final JsonParser parser) throws IOException {
        switch (token) {
        case VALUE_TRUE:
        case VALUE_FALSE:
            return parser.getValueAsBoolean();
        case VALUE_STRING:
            return parser.getValueAsString();
        case VALUE_NUMBER_INT:
            try {
                return parser.getValueAsInt();
            } catch (final JsonParseException e) {
                return parser.getValueAsLong();
            }
        case VALUE_NUMBER_FLOAT:
            return parser.getValueAsDouble();
        case VALUE_NULL:
            return null;
        default:
            throw new MappingException("Could not decode primitve value " + token);
        }
    }

    public void setObjectMapper(final ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void afterPropertiesSet() {
        if (objectMapper == null) {
            objectMapper = new ObjectMapper();
        }
    }

}