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

io.micronaut.serde.oracle.jdbc.json.OracleJdbcJsonParserDecoder Maven / Gradle / Ivy

Go to download

Enables serialization/deserialization in Micronaut applications using build time information

The newest version!
/*
 * Copyright 2017-2021 original 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
 *
 * https://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 io.micronaut.serde.oracle.jdbc.json;

import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.StringUtils;
import io.micronaut.serde.exceptions.InvalidFormatException;
import io.micronaut.serde.exceptions.SerdeException;
import io.micronaut.serde.support.AbstractStreamDecoder;
import oracle.sql.json.OracleJsonArray;
import oracle.sql.json.OracleJsonParser;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;

/**
 * Implementation of the {@link io.micronaut.serde.Decoder} interface for Oracle JDBC JSON.
 *
 * @author Denis Stepanov
 * @since 1.2.0
 */
@Internal
public final class OracleJdbcJsonParserDecoder extends AbstractStreamDecoder {

    private static final String METHOD_CALLED_IN_WRONG_CONTEXT = "Method called in wrong context ";

    private final OracleJsonParser jsonParser;
    private OracleJsonParser.Event currentEvent;

    OracleJdbcJsonParserDecoder(OracleJsonParser jsonParser, RemainingLimits remainingLimits) {
        super(remainingLimits);
        this.jsonParser = jsonParser;
        this.currentEvent = jsonParser.next();
    }

    @Override
    public void finishStructure(boolean consumeLeftElements) throws IOException {
        super.finishStructure(consumeLeftElements);
        nextToken();
    }

    @Override
    protected TokenType currentToken() {
        return switch (currentEvent) {
            case START_ARRAY -> TokenType.START_ARRAY;
            case START_OBJECT -> TokenType.START_OBJECT;
            case KEY_NAME -> TokenType.KEY;
            case VALUE_STRING -> TokenType.STRING;
            case VALUE_DECIMAL, VALUE_DOUBLE, VALUE_FLOAT -> TokenType.NUMBER;
            case VALUE_TRUE, VALUE_FALSE -> TokenType.BOOLEAN;
            case VALUE_NULL -> TokenType.NULL;
            case END_OBJECT -> TokenType.END_OBJECT;
            case END_ARRAY -> TokenType.END_ARRAY;
            default -> TokenType.OTHER;
        };
    }

    @Override
    protected void nextToken() {
        if (jsonParser.hasNext()) {
            currentEvent = jsonParser.next();
        } else {
            // EOF
            currentEvent = null;
        }
    }

    @Override
    protected String getCurrentKey() {
        return jsonParser.getString();
    }

    @Override
    protected String coerceScalarToString(TokenType currentToken) {
        return switch (currentEvent) {
            case VALUE_STRING, VALUE_DECIMAL, VALUE_DOUBLE, VALUE_FLOAT, VALUE_INTERVALDS, VALUE_INTERVALYM ->
                // only allowed for string, number
                // additionally for processing string values from VALUE_INTERVALDS, VALUE_INTERVALYM
                // in combination with custom de/serializers configured for Oracle Json parsing
                // which is needed to transform from VALUE_INTERVALDS to java.time.Duration
                jsonParser.getString();
            case VALUE_BINARY ->
                // VALUE_BINARY will return Base16 encoded string and when serializing just write back the same string value
                // which should work fine
                jsonParser.getValue().asJsonBinary().getString();
            case VALUE_TRUE -> StringUtils.TRUE;
            case VALUE_FALSE -> StringUtils.FALSE;
            default ->
                throw new IllegalStateException(METHOD_CALLED_IN_WRONG_CONTEXT + currentEvent);
        };
    }

    @Override
    protected boolean getBoolean() {
        return currentEvent == OracleJsonParser.Event.VALUE_TRUE;
    }

    @Override
    protected String getString() {
        return jsonParser.getString();
    }

    @Override
    protected long getLong() {
        return jsonParser.getLong();
    }

    @Override
    protected double getDouble() {
        return jsonParser.getBigDecimal().doubleValue();
    }

    @Override
    protected BigInteger getBigInteger() {
        return jsonParser.getBigDecimal().toBigInteger();
    }

    @Override
    protected BigDecimal getBigDecimal() {
        return jsonParser.getBigDecimal();
    }

    @Override
    protected Number getBestNumber() {
        return switch (currentEvent) {
            case VALUE_DECIMAL -> jsonParser.getBigDecimal();
            case VALUE_DOUBLE -> jsonParser.getDouble();
            case VALUE_FLOAT -> jsonParser.getFloat();
            default ->
                throw new IllegalStateException(METHOD_CALLED_IN_WRONG_CONTEXT + currentEvent);
        };
    }

    @Override
    protected void skipChildren() {
        if (currentEvent == OracleJsonParser.Event.START_OBJECT) {
            jsonParser.skipObject();
        } else if (currentEvent == OracleJsonParser.Event.START_ARRAY) {
            jsonParser.skipArray();
        }
    }

    @Override
    @NonNull
    public IOException createDeserializationException(@NonNull String message, @Nullable Object invalidValue) {
        if (invalidValue != null) {
            return new InvalidFormatException(message, null, invalidValue);
        } else {
            return new SerdeException(message);
        }
    }

    /**
     * Decodes Oracle JSON binary data as byte array.
     *
     * @return the byte array for Oracle JSON binary
     */
    @Override
    public byte[] decodeBinary() {
        if (currentEvent == OracleJsonParser.Event.VALUE_BINARY) {
            byte[] bytes = jsonParser.getBytes();
            nextToken();
            return bytes;
        }
        if (currentEvent == OracleJsonParser.Event.START_ARRAY) {
            OracleJsonArray oracleJsonArray = jsonParser.getArray();
            int size = oracleJsonArray.size();
            byte[] bytes = new byte[size];
            for (int i = 0; i < size; i++) {
                if (oracleJsonArray.isNull(i)) {
                    bytes[i] = 0;
                } else {
                    bytes[i] = (byte) oracleJsonArray.getInt(i);
                }
            }
            nextToken();
            return bytes;
        }
        if (currentEvent == OracleJsonParser.Event.VALUE_STRING) {
            String str = jsonParser.getString();
            nextToken();
            // string binary representation is Base16 encoded, so we should decode it
            return decodeBase16(str);
        }
        throw new IllegalStateException(METHOD_CALLED_IN_WRONG_CONTEXT + currentEvent);
    }

    /**
     * Decodes Oracle JSON value as {@link LocalDateTime}.
     *
     * @return the {@link LocalDateTime} value being decoded
     */
    public LocalDateTime decodeLocalDateTime() {
        LocalDateTime value =
            switch (currentEvent) {
                case VALUE_DATE, VALUE_TIMESTAMP -> jsonParser.getLocalDateTime();
                case VALUE_STRING -> LocalDateTime.parse(jsonParser.getString());
                default -> throw new IllegalStateException(METHOD_CALLED_IN_WRONG_CONTEXT + currentEvent);
            };
        nextToken();
        return value;
    }

    /**
     * Decodes Oracle JSON value as {@link OffsetDateTime}.
     *
     * @return the {@link OffsetDateTime} value being decoded
     */
    public OffsetDateTime decodeOffsetDateTime() {
        OffsetDateTime value =
            switch (currentEvent) {
                case VALUE_TIMESTAMPTZ -> jsonParser.getOffsetDateTime();
                case VALUE_STRING -> OffsetDateTime.parse(jsonParser.getString());
                default -> throw new IllegalStateException(METHOD_CALLED_IN_WRONG_CONTEXT + currentEvent);
            };
        nextToken();
        return value;
    }

    /**
     * Decodes Oracle JSON value as {@link ZonedDateTime}.
     *
     * @return the {@link ZonedDateTime} value being decoded
     */
    public ZonedDateTime decodeZonedDateTime() {
        ZonedDateTime value =
            switch (currentEvent) {
                case VALUE_TIMESTAMPTZ -> jsonParser.getOffsetDateTime().toZonedDateTime();
                case VALUE_STRING -> ZonedDateTime.parse(jsonParser.getString());
                default -> throw new IllegalStateException(METHOD_CALLED_IN_WRONG_CONTEXT + currentEvent);
            };
        nextToken();
        return value;
    }

    private static byte[] decodeBase16(CharSequence cs) {
        final int len = cs.length();
        if ((len % 2) != 0) {
            throw new IllegalArgumentException("Encoded string must have an even length");
        }
        byte[] bytes = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            int hi = Character.digit(cs.charAt(i), 16);
            int lo = Character.digit(cs.charAt(i + 1), 16);
            if ((hi | lo) < 0) {
                throw new IllegalArgumentException("Encoded string " + cs + " contains non-hex characters");
            }
            bytes[i / 2] = (byte) (hi << 4 | lo);
        }
        return bytes;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy