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

com.azure.cosmos.implementation.directconnectivity.rntbd.RntbdResponse Maven / Gradle / Ivy

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.cosmos.implementation.directconnectivity.rntbd;

import com.azure.cosmos.implementation.directconnectivity.StoreResponse;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.ReferenceCounted;
import io.netty.util.ResourceLeakDetector;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

import static com.azure.cosmos.implementation.directconnectivity.rntbd.RntbdConstants.RntbdResponseHeader;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.Math.min;

@JsonPropertyOrder({ "frame", "headers", "content" })
public final class RntbdResponse implements ReferenceCounted {

    // region Fields

    @JsonSerialize(using = PayloadSerializer.class)
    @JsonProperty
    private final ByteBuf content;

    @JsonProperty
    private final RntbdResponseStatus frame;

    @JsonProperty
    private final RntbdResponseHeaders headers;

    private final ByteBuf in;

    private final AtomicInteger referenceCount = new AtomicInteger();

    // endregion

    public RntbdResponse(final UUID activityId, final int statusCode, final Map map, final ByteBuf content) {

        this.headers = RntbdResponseHeaders.fromMap(map, content.readableBytes() > 0);
        this.in = Unpooled.EMPTY_BUFFER;
        this.content = content.copy().retain();

        final HttpResponseStatus status = HttpResponseStatus.valueOf(statusCode);
        final int length = RntbdResponseStatus.LENGTH + this.headers.computeLength();

        this.frame = new RntbdResponseStatus(length, status, activityId);
    }

    private RntbdResponse(
        final ByteBuf in, final RntbdResponseStatus frame, final RntbdResponseHeaders headers, final ByteBuf content
    ) {
        this.in = in.retain();
        this.frame = frame;
        this.headers = headers;
        this.content = content.retain();
    }

    @JsonIgnore
    public UUID getActivityId() {
        return this.frame.getActivityId();
    }

    @JsonIgnore
    public ByteBuf getContent() {
        return this.content;
    }

    @JsonIgnore
    public RntbdResponseHeaders getHeaders() {
        return this.headers;
    }

    @JsonIgnore
    public HttpResponseStatus getStatus() {
        return this.frame.getStatus();
    }

    @JsonIgnore
    public Long getTransportRequestId() {
        return this.getHeader(RntbdResponseHeader.TransportRequestID);
    }

    static RntbdResponse decode(final ByteBuf in) {

        final int start = in.markReaderIndex().readerIndex();

        final RntbdResponseStatus frame = RntbdResponseStatus.decode(in);
        final RntbdResponseHeaders headers = RntbdResponseHeaders.decode(in.readSlice(frame.getHeadersLength()));
        final boolean hasPayload = headers.isPayloadPresent();
        final ByteBuf content;

        if (hasPayload) {

            if (!RntbdFramer.canDecodePayload(in)) {
                headers.releaseBuffers();
                in.resetReaderIndex();
                return null;
            }

            content = in.readSlice(in.readIntLE());

        } else {

            content = Unpooled.EMPTY_BUFFER;
        }

        final int end = in.readerIndex();
        in.resetReaderIndex();

        return new RntbdResponse(in.readSlice(end - start), frame, headers, content);
    }

    public void encode(final ByteBuf out) {

        final int start = out.writerIndex();

        this.frame.encode(out);
        this.headers.encode(out);

        final int length = out.writerIndex() - start;
        checkState(length == this.frame.getLength());

        if (this.hasPayload()) {
            out.writeIntLE(this.content.readableBytes());
            out.writeBytes(this.content);
        } else if (this.content.readableBytes() > 0) {
            throw new IllegalStateException();
        }
    }

    @JsonIgnore
    @SuppressWarnings("unchecked")
    public  T getHeader(final RntbdResponseHeader header) {
        return (T)this.headers.get(header).getValue();
    }

    public boolean hasPayload() {
        return this.headers.isPayloadPresent();
    }

    /**
     * Returns the reference count of this object.  If {@code 0}, it means this object has been deallocated.
     */
    @Override
    public int refCnt() {
        return this.referenceCount.get();
    }

    /**
     * Decreases the reference count by {@code 1} and deallocate this response if the count reaches {@code 0}.
     *
     * @return {@code true} if and only if the reference count became {@code 0} and this response is deallocated.
     */
    @Override
    public boolean release() {
        return this.release(1);
    }

    /**
     * Decreases the reference count by {@code decrement} and deallocates this response if the count reaches {@code 0}.
     *
     * @param decrement amount of the decrease.
     * @return {@code true} if and only if the reference count became {@code 0} and this response has been deallocated.
     */
    @Override
    public boolean release(final int decrement) {

        return this.referenceCount.accumulateAndGet(decrement, (value, n) -> {

            value = value - min(value, n);

            if (value == 0) {

                checkState(this.headers != null && this.content != null);
                this.headers.releaseBuffers();

                if (this.in != Unpooled.EMPTY_BUFFER) {
                    this.in.release();
                }

                if (this.content != Unpooled.EMPTY_BUFFER) {
                    this.content.release();
                }

                // TODO: DANOBLE: figure out why PooledUnsafeDirectByteBuf violates these expectations:
                //    checkState(this.in == Unpooled.EMPTY_BUFFER || this.in.refCnt() == 0);
                //    checkState(this.content == Unpooled.EMPTY_BUFFER || this.content.refCnt() == 0);
                //  Specifically, why are this.in.refCnt() and this.content.refCnt() equal to 1?
            }

            return value;

        }) == 0;
    }

    /**
     * Increases the reference count by {@code 1}.
     */
    @Override
    public ReferenceCounted retain() {
        this.referenceCount.incrementAndGet();
        return this;
    }

    /**
     * Increases the reference count by the specified {@code increment}.
     *
     * @param increment amount of the increase
     */
    @Override
    public ReferenceCounted retain(final int increment) {
        this.referenceCount.addAndGet(increment);
        return this;
    }

    StoreResponse toStoreResponse(final RntbdContext context) {

        checkNotNull(context, "context");
        final int length = this.content.readableBytes();

        return new StoreResponse(
            this.getStatus().code(),
            this.headers.asList(context, this.getActivityId()),
            length == 0 ? null : this.content.readCharSequence(length, StandardCharsets.UTF_8).toString()
        );
    }

    @Override
    public String toString() {
        return RntbdObjectMapper.toString(this);
    }

    /**
     * Records the current access location of this object for debugging purposes
     * 

* If this object is determined to be leaked, the information recorded by this operation will be provided to you * via {@link ResourceLeakDetector}. This method is a shortcut to {@link #touch(Object) touch(null)}. */ @Override public ReferenceCounted touch() { return this; } /** * Records the current access location of this object with additional arbitrary information for debugging purposes *

* If this object is determined to be leaked, the information recorded by this operation will be * provided to you via {@link ResourceLeakDetector}. * * @param hint information useful for debugging (unused) */ @Override public ReferenceCounted touch(final Object hint) { return this; } private static class PayloadSerializer extends StdSerializer { public PayloadSerializer() { super(ByteBuf.class, true); } @Override public void serialize(final ByteBuf value, final JsonGenerator generator, final SerializerProvider provider) throws IOException { final int length = value.readableBytes(); generator.writeStartObject(); generator.writeObjectField("lengthInBytes", length); generator.writeObjectField("hexDump", ByteBufUtil.hexDump(value, 0, length)); generator.writeObjectField("string", value.getCharSequence(0, length, StandardCharsets.UTF_8)); generator.writeEndObject(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy