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

com.pcloud.networking.client.RealApiChannel Maven / Gradle / Ivy

/*
 * Copyright (c) 2020 pCloud AG
 *
 * 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.pcloud.networking.client;

import com.pcloud.networking.protocol.BytesReader;
import com.pcloud.networking.protocol.BytesWriter;
import com.pcloud.networking.protocol.DataSource;
import com.pcloud.networking.protocol.ForwardingProtocolRequestWriter;
import com.pcloud.networking.protocol.ForwardingProtocolResponseReader;
import com.pcloud.networking.protocol.ProtocolRequestWriter;
import com.pcloud.networking.protocol.ProtocolResponseReader;
import com.pcloud.networking.protocol.TypeToken;
import okio.BufferedSink;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.ClosedChannelException;

import static com.pcloud.utils.IOUtils.closeQuietly;

class RealApiChannel implements ApiChannel {

    private ConnectionProvider connectionProvider;
    private Connection connection;
    private ProtocolRequestWriter writer;
    private ProtocolResponseReader reader;
    private final Endpoint endpoint;
    private long startedRequests = 0L;
    private long startedResponses = 0L;
    private long completedRequests = 0L;
    private long completedResponses = 0L;
    private volatile boolean closed;

    private final Object counterLock = new Object();

    RealApiChannel(ConnectionProvider connectionProvider, Endpoint endpoint) throws IOException {
        this.connectionProvider = connectionProvider;
        this.connection = connectionProvider.obtainConnection(endpoint);
        this.endpoint = connection.endpoint();
        this.writer = new CountingProtocolRequestWriter(new BytesWriter(connection.sink()), this);
        this.reader = new CountingProtocolResponseReader(new BytesReader(connection.source()), this);
    }

    @Override
    public Endpoint endpoint() {
        return endpoint;
    }

    @Override
    public ProtocolResponseReader reader() {
        return reader;
    }

    @Override
    public ProtocolRequestWriter writer() {
        return writer;
    }

    @Override
    public boolean isOpen() {
        return !closed;
    }

    @Override
    public boolean isIdle() {
        synchronized (counterLock) {
            return startedRequests == completedRequests &&
                    startedResponses == completedResponses &&
                    completedRequests == completedResponses;
        }
    }

    @Override
    public void close() {
        if (!closed) {
            // Lock to avoid recycling the same connection twice
            // by competing threads.
            synchronized (this) {
                if (!closed) {
                    closed = true;
                    if (isIdle()) {
                        connectionProvider.recycleConnection(connection);
                    } else {
                        closeQuietly(connection);
                    }
                    connection = null;
                }
            }
        }
    }

    private void checkNotClosed() throws ClosedChannelException {
        if (closed) {
            throw new ClosedChannelException();
        }
    }

    private void startRequest() {
        synchronized (counterLock) {
            startedRequests++;
        }
    }

    private void completeRequest() {
        synchronized (counterLock) {
            completedRequests++;
        }
    }

    private void startResponse() {
        synchronized (counterLock) {
            startedResponses++;
        }
    }

    private void completeResponse() {
        synchronized (counterLock) {
            completedResponses++;
        }
    }

    private static class CountingProtocolRequestWriter extends ForwardingProtocolRequestWriter {

        private RealApiChannel apiChannel;

        CountingProtocolRequestWriter(ProtocolRequestWriter delegate, RealApiChannel apiChannel) {
            super(delegate);
            this.apiChannel = apiChannel;
        }

        @Override
        public ProtocolRequestWriter beginRequest() throws IOException {
            apiChannel.checkNotClosed();
            apiChannel.startRequest();
            super.beginRequest();
            return this;
        }

        @Override
        public ProtocolRequestWriter writeData(DataSource source) throws IOException {
            apiChannel.checkNotClosed();
            super.writeData(source);
            return this;
        }

        @Override
        public ProtocolRequestWriter writeMethodName(String name) throws IOException {
            apiChannel.checkNotClosed();
            super.writeMethodName(name);
            return this;
        }

        @Override
        public ProtocolRequestWriter writeName(String name) throws IOException {
            apiChannel.checkNotClosed();
            super.writeName(name);
            return this;
        }

        @Override
        public ProtocolRequestWriter writeValue(Object value) throws IOException {
            apiChannel.checkNotClosed();
            super.writeValue(value);
            return this;
        }

        @Override
        public ProtocolRequestWriter writeValue(String value) throws IOException {
            apiChannel.checkNotClosed();
            super.writeValue(value);
            return this;
        }

        @Override
        public ProtocolRequestWriter writeValue(double value) throws IOException {
            apiChannel.checkNotClosed();
            super.writeValue(value);
            return this;
        }

        @Override
        public ProtocolRequestWriter writeValue(float value) throws IOException {
            apiChannel.checkNotClosed();
            super.writeValue(value);
            return this;
        }

        @Override
        public ProtocolRequestWriter writeValue(long value) throws IOException {
            apiChannel.checkNotClosed();
            super.writeValue(value);
            return this;
        }

        @Override
        public ProtocolRequestWriter writeValue(boolean value) throws IOException {
            apiChannel.checkNotClosed();
            super.writeValue(value);
            return this;
        }

        @Override
        public void close() {
            apiChannel.close();
        }

        @Override
        public ProtocolRequestWriter endRequest() throws IOException {
            apiChannel.checkNotClosed();
            // Mark request as complete before completing it to avoid
            // race conditions between closing the channel and completing the actual write
            // to the underlying sink. By marking in advance the case in question materializes,
            // the channel's connection will not be reused without being sure it's clean and
            // there aren't any unfinished request/response pairs.
            apiChannel.completeRequest();
            super.endRequest();
            return this;
        }
    }

    private static class CountingProtocolResponseReader extends ForwardingProtocolResponseReader {

        private RealApiChannel apiChannel;

        CountingProtocolResponseReader(ProtocolResponseReader delegate, RealApiChannel apiChannel) {
            super(delegate);
            this.apiChannel = apiChannel;
        }

        @Override
        public long beginResponse() throws IOException {
            apiChannel.checkNotClosed();
            apiChannel.startResponse();
            return super.beginResponse();
        }

        @Override
        public boolean endResponse() throws IOException {
            apiChannel.checkNotClosed();
            boolean hasData = super.endResponse();
            if (!hasData) {
                apiChannel.completeResponse();
            }
            return hasData;
        }

        @Override
        public void readData(BufferedSink sink) throws IOException {
            apiChannel.checkNotClosed();
            super.readData(sink);
            apiChannel.completeResponse();
        }

        @Override
        public void readData(OutputStream outputStream) throws IOException {
            apiChannel.checkNotClosed();
            super.readData(outputStream);
            apiChannel.completeResponse();
        }

        @Override
        public TypeToken peek() throws IOException {
            apiChannel.checkNotClosed();
            return super.peek();
        }

        @Override
        public void beginObject() throws IOException {
            apiChannel.checkNotClosed();
            super.beginObject();
        }

        @Override
        public void beginArray() throws IOException {
            apiChannel.checkNotClosed();
            super.beginArray();
        }

        @Override
        public void endArray() throws IOException {
            apiChannel.checkNotClosed();
            super.endArray();
        }

        @Override
        public void endObject() throws IOException {
            apiChannel.checkNotClosed();
            super.endObject();
        }

        @Override
        public boolean readBoolean() throws IOException {
            apiChannel.checkNotClosed();
            return super.readBoolean();
        }

        @Override
        public String readString() throws IOException {
            apiChannel.checkNotClosed();
            return super.readString();
        }

        @Override
        public long readNumber() throws IOException {
            apiChannel.checkNotClosed();
            return super.readNumber();
        }

        @Override
        public void close() {
            // Let the ApiChannel decide how to release held resources.
            apiChannel.close();
        }

        @Override
        public boolean hasNext() throws IOException {
            apiChannel.checkNotClosed();
            return super.hasNext();
        }

        @Override
        public void skipValue() throws IOException {
            apiChannel.checkNotClosed();
            super.skipValue();
        }

        @Override
        public ProtocolResponseReader newPeekingReader() {
            return new CountingProtocolResponseReader(super.newPeekingReader(), apiChannel);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy