io.soliton.protobuf.json.JsonRpcClientHandler.java Source code

Java tutorial

Introduction

Here is the source code for io.soliton.protobuf.json.JsonRpcClientHandler.java

Source

/**
 * Copyright 2013 Julien Silland
 *
 * 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 io.soliton.protobuf.json;

import io.soliton.protobuf.ClientLogger;
import io.soliton.protobuf.ClientMethod;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.collect.MapMaker;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.JsonReader;
import com.google.protobuf.Message;
import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;

import java.io.InputStreamReader;
import java.util.Random;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;

/**
 * Client handler in charge of decoding the server's response and dispatching
 * it to the relevant client.
 *
 * @author Julien Silland (julien@soliton.io)
 */
class JsonRpcClientHandler extends SimpleChannelInboundHandler<HttpResponse> {

    private static final Logger logger = Logger.getLogger(JsonRpcClientHandler.class.getCanonicalName());
    private static final Random RANDOM = new Random();

    private final ConcurrentMap<Long, JsonResponseFuture<? extends Message>> inFlightRequests = new MapMaker()
            .makeMap();
    private ClientLogger clientLogger;

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpResponse response)
            throws Exception {
        if (!(response instanceof HttpContent)) {
            clientLogger.logServerError(null, null, new Exception("Returned response has no content"));
            logger.severe("Returned response has no content");
            return;
        }

        HttpContent content = (HttpContent) response;

        if (!response.headers().get(HttpHeaders.Names.CONTENT_TYPE).equals(JsonRpcProtocol.CONTENT_TYPE)) {
            logger.warning("Incorrect Content-Type: " + response.headers().get(HttpHeaders.Names.CONTENT_TYPE));
        }

        JsonElement root;
        try {
            ByteBufInputStream stream = new ByteBufInputStream(content.content());
            JsonReader reader = new JsonReader(new InputStreamReader(stream, Charsets.UTF_8));
            root = new JsonParser().parse(reader);
        } catch (JsonSyntaxException jsonException) {
            clientLogger.logServerError(null, null, jsonException);
            logger.warning("JSON response cannot be decoded");
            return;
        }
        if (!root.isJsonObject()) {
            logger.warning("JSON response is not a JSON object: " + root.toString());
            return;
        }

        JsonRpcResponse jsonRpcResponse = JsonRpcResponse.fromJson(root.getAsJsonObject());

        JsonElement requestId = jsonRpcResponse.id();
        if (requestId == null || !requestId.isJsonPrimitive()) {
            clientLogger.logServerError(null, null,
                    new Exception("Received response identifier is not JSON primitive"));
            logger.warning("Received response identifier is not JSON primitive: " + requestId.toString());
            return;
        }

        JsonResponseFuture<? extends Message> future = inFlightRequests.remove(requestId.getAsLong());
        if (future == null) {
            clientLogger.logServerError(null, null, new Exception("Response received for unknown identifier"));
            logger.severe("Response received for unknown identifier: " + requestId.getAsLong());
            return;
        }

        clientLogger.logSuccess(future.method());
        future.setResponse(jsonRpcResponse);
    }

    /**
     * Returns a new provisional response suited to receive results of a given
     * protobuf message type.
     *
     * @param method the method invocation for which a response should be created
     * @param <O> the type of the message this response will handle
     */
    <O extends Message> JsonResponseFuture<O> newProvisionalResponse(ClientMethod<O> method) {
        long requestId = RANDOM.nextLong();
        JsonResponseFuture<O> outputFuture = new JsonResponseFuture<>(requestId, method);
        inFlightRequests.put(requestId, outputFuture);
        return outputFuture;
    }

    /**
     * Signals to this handler to release any resource associated with the
     * given request identifier.
     *
     * @param requestId the request identifier
     */
    void finish(long requestId) {
        inFlightRequests.remove(requestId);
    }

    @VisibleForTesting
    ConcurrentMap<Long, JsonResponseFuture<? extends Message>> inFlightRequests() {
        return inFlightRequests;
    }

    public void setClientLogger(ClientLogger clientLogger) {
        this.clientLogger = clientLogger;
    }
}