com.ebuddy.cassandra.structure.StructureConverter.java Source code

Java tutorial

Introduction

Here is the source code for com.ebuddy.cassandra.structure.StructureConverter.java

Source

/*
 * Copyright 2013 eBuddy B.V.
 *
 *    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.ebuddy.cassandra.structure;

import static org.apache.commons.lang3.ObjectUtils.NULL;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PushbackReader;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Converter for embedded objects in values encoded as bytes.
 *
 * @author Eric Zoerner <a href="mailto:ezoerner@ebuddy.com">ezoerner@ebuddy.com</a>
 */
public class StructureConverter {
    private static final String UTF_8 = "UTF-8";
    private static final Charset UTF8_CHARSET = Charset.forName(UTF_8);
    private static final StructureConverter INSTANCE = new StructureConverter();
    protected static final ObjectMapper JSON_MAPPER = new ObjectMapper();

    /**
     * Header char, a unicode non-character, used to flag a JSON deserialized object.
     */
    private static final int HEADER_CHAR = '\uFFFE';
    /**
     * utf-8 encoded bytes for HEADER_CHAR.
     */
    private static final byte[] UTF8_HEADER_BYTES = { (byte) 0xef, (byte) 0xbf, (byte) 0xbe };

    /**
     * Only instantiated once for the static singleton.
     */
    private StructureConverter() {
    }

    /**
     * Get the singleton instance of StructureConverter.
     *
     * @return the singleton StructureConverter
     */
    public static StructureConverter get() {
        return INSTANCE;
    }

    /**
     * @throws DataFormatException if format of the string is incorrect and could not be parsed as JSON
     */
    public Object fromString(String str) {
        if (str == null) {
            return null;
        }
        return decodeBytes(str.getBytes(UTF8_CHARSET));
    }

    /**
     * @throws DataFormatException if the object could not be encoded as JSON
     */
    public String toString(Object obj) {
        if (obj == null) {
            return null;
        }

        if (obj instanceof String) {
            return (String) obj;
        }

        // write as special header bytes followed by JSON
        // intercept the Null token which stands in for a real null
        if (obj == NULL) {
            obj = null;
        }

        String jsonString = encodeJson(obj);
        char[] chars = new char[jsonString.length() + 1];
        chars[0] = HEADER_CHAR;
        jsonString.getChars(0, jsonString.length(), chars, 1);
        return new String(chars);
    }

    /**
     * @throws DataFormatException if object cannot be encoded as JSON
     */
    public ByteBuffer toByteBuffer(Object obj) {
        if (obj == null) {
            return null;
        }

        if (obj instanceof String) {
            return ByteBuffer.wrap(((String) obj).getBytes(UTF8_CHARSET));
        }

        // write as special header bytes followed by JSON
        // intercept the Null token which stands in for a real null
        if (obj == NULL) {
            obj = null;
        }
        String jsonString = encodeJson(obj);
        byte[] jsonBytes = jsonString.getBytes(UTF8_CHARSET);
        byte[] result = new byte[jsonBytes.length + UTF8_HEADER_BYTES.length];
        System.arraycopy(UTF8_HEADER_BYTES, 0, result, 0, UTF8_HEADER_BYTES.length);
        System.arraycopy(jsonBytes, 0, result, UTF8_HEADER_BYTES.length, jsonBytes.length);
        return ByteBuffer.wrap(result);
    }

    /**
     * @throws DataFormatException is data in the byte buffer is incorrect and cannot be decoded
     */
    public Object fromByteBuffer(ByteBuffer byteBuffer) {
        if (byteBuffer == null) {
            return null;
        }
        byte[] bytes = new byte[byteBuffer.remaining()];
        byteBuffer.get(bytes, 0, bytes.length);
        return decodeBytes(bytes);
    }

    @SuppressWarnings("fallthrough")
    private Object decodeBytes(byte[] bytes) {
        if (bytes.length == 0) {
            return "";
        }

        // look for header char to determine if a JSON object or legacy NestedProperties
        PushbackReader reader = new PushbackReader(
                new InputStreamReader(new ByteArrayInputStream(bytes), UTF8_CHARSET));

        try {
            int firstChar = reader.read();
            switch (firstChar) {
            case '\uFFFF':
                // legacy NestedProperties, obsolete and interpreted now as simply a JSON encoded Map or
                // beginning of a list terminator

                // if the second character is \uFFFF then this is a list terminator value and just return it
                int secondChar = reader.read();
                if (secondChar == '\uFFFF') {
                    return Types.LIST_TERMINATOR_VALUE;
                }
                if (secondChar == -1) {
                    throw new DataFormatException("Found header FFFF but no data");
                }
                reader.unread(secondChar);
                // fall through and read as a JSON object

            case HEADER_CHAR:
                try {
                    return JSON_MAPPER.readValue(reader, Object.class);
                } catch (IOException e) {
                    throw new DataFormatException("Could not parse JSON", e);
                }

            default:
                // if no special header, then bytes are just a string
                return new String(bytes, UTF8_CHARSET);
            }
        } catch (IOException ioe) {
            throw new DataFormatException("Could read data", ioe);
        }
    }

    private String encodeJson(Object value) {
        String jsonString;
        try {
            jsonString = JSON_MAPPER.writeValueAsString(value);
            return jsonString;
        } catch (IOException ioe) {
            throw new DataFormatException("Could not encode object as JSON: class=" + value.getClass().getName(),
                    ioe);
        }
    }
}