All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.couchbase.client.java.transcoder.TranscoderUtils Maven / Gradle / Ivy

/**
 * Copyright (C) 2014 Couchbase, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING
 * IN THE SOFTWARE.
 */
package com.couchbase.client.java.transcoder;

import com.couchbase.client.core.endpoint.util.WhitespaceSkipper;
import com.couchbase.client.core.logging.CouchbaseLogger;
import com.couchbase.client.core.logging.CouchbaseLoggerFactory;
import com.couchbase.client.core.message.ResponseStatus;
import com.couchbase.client.deps.com.fasterxml.jackson.databind.ObjectMapper;
import com.couchbase.client.deps.io.netty.buffer.ByteBuf;
import com.couchbase.client.deps.io.netty.buffer.ByteBufUtil;
import com.couchbase.client.deps.io.netty.buffer.Unpooled;
import com.couchbase.client.java.document.json.JsonArray;
import com.couchbase.client.java.document.json.JsonObject;

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

/**
 * Helper methods and flags for the shipped {@link Transcoder}s.
 *
 * @author Michael Nitschinger
 * @author Simon Baslé
 * @since 2.0
 */
public class TranscoderUtils {

    private static final CouchbaseLogger LOGGER = CouchbaseLoggerFactory.getInstance(TranscoderUtils.class);

    /**
     * 32bit flag is composed of:
     *  - 3 compression bits
     *  - 1 bit reserved for future use
     *  - 4 format flags bits. those 8 upper bits make up the common flags
     *  - 8 bits reserved for future use
     *  - 16 bits for legacy flags
     *
     * This mask allows to compare a 32 bits flags with the 4 common flag format bits
     * ("00001111 00000000 00000000 00000000").
     *
     * @see #extractCommonFlags(int)
     * @see #hasCommonFlags(int)
     * @see #hasCompressionFlags(int)
     */
    public static final int COMMON_FORMAT_MASK = 0x0F000000;

    public static final int PRIVATE_COMMON_FLAGS = createCommonFlags(CommonFlags.PRIVATE.ordinal());
    public static final int JSON_COMMON_FLAGS = createCommonFlags(CommonFlags.JSON.ordinal());
    public static final int BINARY_COMMON_FLAGS = createCommonFlags(CommonFlags.BINARY.ordinal());
    public static final int STRING_COMMON_FLAGS = createCommonFlags(CommonFlags.STRING.ordinal());

    public static final int SERIALIZED_LEGACY_FLAGS = 1;
    public static final int BINARY_LEGACY_FLAGS = (8 << 8);
    public static final int STRING_LEGACY_FLAGS = 0;
    public static final int JSON_LEGACY_FLAGS = STRING_LEGACY_FLAGS;
    public static final int BOOLEAN_LEGACY_FLAGS = STRING_LEGACY_FLAGS;
    public static final int LONG_LEGACY_FLAGS = STRING_LEGACY_FLAGS;
    public static final int DOUBLE_LEGACY_FLAGS = STRING_LEGACY_FLAGS;


    public static final int SERIALIZED_COMPAT_FLAGS = PRIVATE_COMMON_FLAGS  | SERIALIZED_LEGACY_FLAGS;
    public static final int JSON_COMPAT_FLAGS       = JSON_COMMON_FLAGS     | JSON_LEGACY_FLAGS;
    public static final int BINARY_COMPAT_FLAGS     = BINARY_COMMON_FLAGS   | BINARY_LEGACY_FLAGS;
    public static final int BOOLEAN_COMPAT_FLAGS    = JSON_COMMON_FLAGS     | BOOLEAN_LEGACY_FLAGS;
    public static final int LONG_COMPAT_FLAGS       = JSON_COMMON_FLAGS     | LONG_LEGACY_FLAGS;
    public static final int DOUBLE_COMPAT_FLAGS     = JSON_COMMON_FLAGS     | DOUBLE_LEGACY_FLAGS;
    public static final int STRING_COMPAT_FLAGS     = STRING_COMMON_FLAGS   | STRING_LEGACY_FLAGS;

    /**
     * Checks whether the upper 8 bits are set, indicating common flags presence.
     *
     * It does this by shifting bits to the right until only the most significant
     * bits are remaining and then checks if one of them is set.
     *
     * @param flags the flags to check.
     * @return true if set, false otherwise.
     */
    public static boolean hasCommonFlags(final int flags) {
        return (flags >> 24) > 0;
    }

    /**
     * Checks whether the upper 3 bits are set, indicating compression presence.
     *
     * It does this by shifting bits to the right until only the most significant
     * bits are remaining and then checks if one of them is set.
     *
     * @param flags the flags to check.
     * @return true if compression set, false otherwise.
     */
    public static boolean hasCompressionFlags(final int flags) {
        return (flags >> 29) > 0;
    }

    /**
     * Checks that flags has common flags bits set and that they correspond to expected common flags format.
     *
     * @param flags the 32 bits flags to check
     * @param expectedCommonFlag the expected common flags format bits
     * @return true if common flags bits are set and correspond to expectedCommonFlag format
     */
    public static boolean hasCommonFormat(final int flags,
        final int expectedCommonFlag) {
        return hasCommonFlags(flags) && (flags & COMMON_FORMAT_MASK) == expectedCommonFlag;
    }

    /**
     * Returns only the common flags from the full flags.
     *
     * @param flags the flags to check.
     * @return only the common flags simple representation (8 bits).
     */
    public static int extractCommonFlags(final int flags) {
        return flags >> 24;
    }

    /**
     * Takes a integer representation of flags and moves them to the common flags MSBs.
     *
     * @param flags the flags to shift.
     * @return an integer having the common flags set.
     */
    public static int createCommonFlags(final int flags) {
        return flags << 24;
    }

    /**
     * Utility method to correctly check a flag has a certain type, by checking
     * that either the corresponding flags are set in the common flags bits or
     * the flag is a legacy flag of the correct type.
     *
     * @param flags the flags to be checked.
     * @param expectedCommonFlags the common flags for the expected type
     * @param expectedLegacyFlag the legacy flags for the expected type
     * @return true if flags conform to the correct common flags or legacy flags
     */
    private static boolean hasFlags(final int flags, final int expectedCommonFlags,
        final int expectedLegacyFlag) {
        return hasCommonFormat(flags, expectedCommonFlags) || flags == expectedLegacyFlag;
    }

    /**
     * Checks if the flags identify a JSON document.
     *
     * This method is strict if it finds common flags set, and if not falls back
     * to a check of legacy JSON string (identified by 0 flags and no compression).
     *
     * @param flags the flags to check.
     * @return true if JSON, false otherwise.
     */
    public static boolean hasJsonFlags(final int flags) {
        return hasFlags(flags, JSON_COMMON_FLAGS, JSON_LEGACY_FLAGS);
    }

    /**
     * Checks if the flags identify a String document.
     *
     * This method is strict if it finds common flags set, and if not falls back
     * to a check of legacy String (identified by 0 flags and no compression).
     *
     * @param flags the flags to check.
     * @return true if String, false otherwise.
     */
    public static boolean hasStringFlags(final int flags) {
        return hasFlags(flags, STRING_COMMON_FLAGS, STRING_LEGACY_FLAGS);
    }

    /**
     * Checks if the flags identify a serialized document.
     *
     * @param flags the flags to check.
     * @return true if serializable, false otherwise.
     */
    public static boolean hasSerializableFlags(final int flags) {
        return hasFlags(flags, PRIVATE_COMMON_FLAGS, SERIALIZED_LEGACY_FLAGS);
    }

    /**
     * Checks if the flags identify a binary document.
     *
     * @param flags the flags to check.
     * @return true if binary, false otherwise.
     */
    public static boolean hasBinaryFlags(final int flags) {
        return hasFlags(flags, BINARY_COMMON_FLAGS, BINARY_LEGACY_FLAGS);
    }

    /**
     * Takes the input content and deserializes it.
     *
     * @param content the content to deserialize.
     * @return the serializable object.
     * @throws Exception if something goes wrong during deserialization.
     */
    public static Serializable deserialize(final ByteBuf content) throws Exception {
        byte[] serialized = new byte[content.readableBytes()];
        content.getBytes(0, serialized);
        ByteArrayInputStream bis = new ByteArrayInputStream(serialized);
        ObjectInputStream is = new ObjectInputStream(bis);
        Serializable deserialized = (Serializable) is.readObject();
        is.close();
        bis.close();
        return deserialized;
    }

    /**
     * Serializes the input into a ByteBuf.
     *
     * @param serializable the object to serialize.
     * @return the serialized object.
     * @throws Exception if something goes wrong during serialization.
     */
    public static ByteBuf serialize(final Serializable serializable) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();;
        ObjectOutputStream os = new ObjectOutputStream(bos);
        os.writeObject(serializable);

        byte[] serialized = bos.toByteArray();
        os.close();
        bos.close();
        return Unpooled.buffer().writeBytes(serialized);
    }

    /**
     * Helper method to encode a String into UTF8 via fast-path methods.
     *
     * @param source the source document.
     * @return the encoded byte buffer.
     */
    public static ByteBuf encodeStringAsUtf8(String source) {
        ByteBuf target = Unpooled.buffer(source.length());
        ByteBufUtil.writeUtf8(target, source);
        return target;
    }

    /**
     * A class that holds information from a {@link ByteBuf} that allows to
     * read its corresponding byte array. Offset and length are needed in case
     * the ByteBuf is directly backed by a byte[] but the size of the byte isn't
     * representative of the actual size of the current content.
     */
    public static class ByteBufToArray {
        public final byte[] byteArray;
        public final int offset;
        public final int length;

        public ByteBufToArray(byte[] byteArray, int offset, int length) {
            this.byteArray = byteArray;
            this.offset = offset;
            this.length = length;
        }
    }

    /**
     * Converts a {@link ByteBuf} to a byte[] in the most straightforward manner available.
     * @param input the ByteBuf to convert.
     * @return a {@link ByteBufToArray} containing the byte[] array, as well as the offset and length to use (in case
     * the actual array is longer than the data the ByteBuf represents for instance).
     */
    public static ByteBufToArray byteBufToByteArray(ByteBuf input) {
        byte[] inputBytes;
        int offset = 0;
        int length = input.readableBytes();
        if (input.hasArray()) {
            inputBytes = input.array();
            offset = input.arrayOffset() + input.readerIndex();
        } else {
            inputBytes = new byte[length];
            input.getBytes(input.readerIndex(), inputBytes);
        }
        return new ByteBufToArray(inputBytes, offset, length);
    }

    /**
     * Decode a {@link ByteBuf} representing a valid JSON entity to the requested target class,
     * using the {@link ObjectMapper} provided and without releasing the buffer.
     *
     * Mapper uses the byte[], performing the most straightforward conversion from ByteBuf to byte[] available. (see
     * {@link #byteBufToByteArray(ByteBuf)}).
     *
     * @param input the ByteBuf to decode.
     * @param clazz the class to decode to.
     * @param mapper the mapper to use for decoding.
     * @param  the decoded type.
     * @return the decoded value.
     * @throws IOException in case decoding failed.
     */
    public static  T byteBufToClass(ByteBuf input, Class clazz, ObjectMapper mapper) throws IOException {
        ByteBufToArray toArray = byteBufToByteArray(input);
        return mapper.readValue(toArray.byteArray, toArray.offset, toArray.length, clazz);
    }

    /**
     * Converts a {@link ByteBuf} representing a valid JSON entity to a generic {@link Object},
     * without releasing the buffer. The entity can either be a JSON object, array or scalar value,
     * potentially with leading whitespace (which gets ignored). JSON objects are converted to a {@link JsonObject}
     * and JSON arrays to a {@link JsonArray}.
     *
     * Detection of JSON objects and arrays is attempted in order not to incur an
     * additional conversion step (JSON to Map to JsonObject for example), but if a
     * Map or List is produced, it will be transformed to {@link JsonObject} or
     * {@link JsonArray} (with a warning logged).
     *
     * @param input the buffer to convert. It won't be released.
     * @return a Object decoded from the buffer
     * @throws IOException if the decoding fails.
     */
    public static Object byteBufToGenericObject(ByteBuf input, ObjectMapper mapper) throws IOException {
        //skip leading whitespaces
        int toSkip = input.forEachByte(new WhitespaceSkipper());
        if (toSkip > 0) {
            input.skipBytes(toSkip);
        }
        //peek into the buffer for quick detection of objects and arrays
        input.markReaderIndex();
        byte first = input.readByte();
        input.resetReaderIndex();

        switch (first) {
            case '{':
                return byteBufToClass(input, JsonObject.class, mapper);
            case '[':
                return byteBufToClass(input, JsonArray.class, mapper);
        }

        //we couldn't fast detect the type, we'll have to unmarshall to object and make sure maps and lists
        //are converted to JsonObject/JsonArray.
        Object value = byteBufToClass(input, Object.class, mapper);
        if (value instanceof Map) {
            LOGGER.warn("A JSON object could not be fast detected (first byte '" + (char) first + "')");
            return JsonObject.from((Map) value);
        } else if (value instanceof List) {
            LOGGER.warn("A JSON array could not be fast detected (first byte '" + (char) first + "')");
            return JsonArray.from((List) value);
        } else {
            return value;
        }
    }

    /**
     * The common flags enum.
     */
    public static enum CommonFlags {
        RESERVED,
        PRIVATE,
        JSON,
        BINARY,
        STRING
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy