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

com.vmware.ovsdb.jsonrpc.v1.service.impl.JsonRpcV1ClientImpl Maven / Gradle / Ivy

There is a newer version: 1.0.1
Show newest version
/*
 * Copyright (c) 2018 VMware, Inc. All Rights Reserved.
 *
 * This product is licensed to you under the BSD-2 license (the "License").
 * You may not use this product except in compliance with the BSD-2 License.
 *
 * This product may include a number of subcomponents with separate copyright
 * notices and license terms. Your use of these subcomponents is subject to the
 * terms and conditions of the subcomponent's license, as noted in the LICENSE
 * file.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

package com.vmware.ovsdb.jsonrpc.v1.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.vmware.ovsdb.jsonrpc.v1.exception.JsonRpcConnectionClosedException;
import com.vmware.ovsdb.jsonrpc.v1.exception.JsonRpcDuplicateIdException;
import com.vmware.ovsdb.jsonrpc.v1.exception.JsonRpcException;
import com.vmware.ovsdb.jsonrpc.v1.exception.JsonRpcInvalidResponseException;
import com.vmware.ovsdb.jsonrpc.v1.exception.JsonRpcResultTypeMismatchException;
import com.vmware.ovsdb.jsonrpc.v1.exception.JsonRpcTransportException;
import com.vmware.ovsdb.jsonrpc.v1.model.JsonRpcV1Request;
import com.vmware.ovsdb.jsonrpc.v1.model.JsonRpcV1Response;
import com.vmware.ovsdb.jsonrpc.v1.service.JsonRpcV1Client;
import com.vmware.ovsdb.jsonrpc.v1.spi.JsonRpcTransporter;
import com.vmware.ovsdb.jsonrpc.v1.util.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.util.Calendar;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * An implementation of {@link JsonRpcV1Client} interface. It depends on a {@link
 * JsonRpcTransporter} to send the requests to the remote server. Whenever the client receives an
 * invalid response, it will close the connection by {@link JsonRpcTransporter#close()}. All
 * un-answered request will result in exception. After the connection is closed, the client cannot
 * be used anymore and all following request will result in
 * {@link JsonRpcConnectionClosedException}.
 *
 * 

The implementation is thread-safe.

*/ public class JsonRpcV1ClientImpl implements JsonRpcV1Client { private static final Logger LOGGER = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass()); private static final long DEFAULT_MAX_TIMEOUT = 300; private static final TimeUnit DEFAULT_MAX_TIMEOUT_UNIT = TimeUnit.SECONDS; private final long maxTimeout; private final TimeUnit maxTimeoutUnit; private final JsonRpcTransporter transporter; private final ConcurrentMap callContexts = new ConcurrentHashMap<>(); private final AtomicBoolean isActive = new AtomicBoolean(true); private final ScheduledExecutorService scheduler; /** * Construct a JsonRpcV1ClientImpl object. If the user calls {@link CompletableFuture#get()} on * the {@link CompletableFuture} returned by {@link JsonRpcV1Client#call(String, String, Class, * Object...)}, it is guaranteed that the get() will be returned within maxTimeout. If the user * calls {@link CompletableFuture#get(long, TimeUnit)}, the timeout value should be less than or * equal to maxTimeout. Or it will still return within maxTimeout. * * @param transporter a {@link JsonRpcTransporter} used to send outgoing requests * @param scheduler a scheduler used to run any asynchronous task * @param maxTimeout maximum timeout of each call * @param maxTimeoutUnit the time unit of the maxTimeout parameter */ public JsonRpcV1ClientImpl( JsonRpcTransporter transporter, ScheduledExecutorService scheduler, long maxTimeout, TimeUnit maxTimeoutUnit ) { this.transporter = transporter; this.scheduler = scheduler; this.maxTimeout = maxTimeout; this.maxTimeoutUnit = maxTimeoutUnit; } /** * Construct a JsonRpcV1ClientImpl object. The default maximum timeout for each call will be used. * To specify the timeout, see {@link JsonRpcV1ClientImpl#JsonRpcV1ClientImpl(JsonRpcTransporter, * ScheduledExecutorService, long, TimeUnit)}. * * @param transporter a {@link JsonRpcTransporter} used to send outgoing requests. * @param scheduler a scheduler used to run any asynchronous task */ public JsonRpcV1ClientImpl(JsonRpcTransporter transporter, ScheduledExecutorService scheduler) { this(transporter, scheduler, DEFAULT_MAX_TIMEOUT, DEFAULT_MAX_TIMEOUT_UNIT); } @Override public CompletableFuture call( String id, String method, Class returnType, Object... params ) throws JsonRpcException { throwExceptionIfNotActive(); JsonNode request = JsonUtil.toJsonNode(new JsonRpcV1Request(id, method, params)); CompletableFuture completableFuture = new CompletableFuture<>(); CallContext callContext = new CallContext<>(completableFuture, returnType); if (callContexts.putIfAbsent(id, callContext) != null) { LOGGER.error("Duplicate call id {} in request {}", id, request); throw new JsonRpcDuplicateIdException("Duplicate call id " + id); } // TODO: After upgrade to Java 9, change this to // completableFuture.orTimeout(maxTimeout, maxTimeoutUnit); // completableFuture.exceptionally(ex -> { // if (ex instanceof TimeoutException) { // callContexts.remove(id); // } // return null; // }); ScheduledFuture timeoutFuture = scheduler.schedule(() -> { completableFuture.completeExceptionally( new TimeoutException("Request " + id + " timeout at " + Calendar.getInstance().getTime())); callContexts.remove(id); }, maxTimeout, maxTimeoutUnit); callContext.setTimeoutFuture(timeoutFuture); try { sendRequest(request); } catch (JsonRpcTransportException ex) { timeoutFuture.cancel(true); callContexts.remove(id); throw ex; } return completableFuture; } @Override public void notify(String method, Object... params) throws JsonRpcException { throwExceptionIfNotActive(); JsonNode request = JsonUtil.toJsonNode(new JsonRpcV1Request(null, method, params)); sendRequest(request); } @Override public void handleResponse(JsonNode responseNode) throws JsonRpcException { throwExceptionIfNotActive(); JsonRpcV1Response jsonRpcV1Response; try { jsonRpcV1Response = JsonUtil.treeToValue(responseNode, JsonRpcV1Response.class); } catch (JsonProcessingException ex) { LOGGER.error("Invalid response {}. Closing the client.", responseNode); shutdown(); throw new JsonRpcInvalidResponseException("Invalid response " + responseNode, ex); } String id = jsonRpcV1Response.getId(); if (id == null) { // Ignore response without ID LOGGER.warn("Response {} doesn't have an ID. Ignore.", jsonRpcV1Response); return; } CallContext callContext = callContexts.remove(id); if (callContext == null) { // Ignore response with unknown ID LOGGER.warn("Unknown response {}", jsonRpcV1Response); return; } // Cancel the timeout future since we have received the response callContext.getTimeoutFuture().cancel(true); CompletableFuture completableFuture = callContext.getCompletableFuture(); String error = jsonRpcV1Response.getError(); if (error != null) { completableFuture.completeExceptionally(new JsonRpcException(error)); } else { JsonNode resultNode = jsonRpcV1Response.getResult(); Class returnType = callContext.getReturnType(); try { Object result = JsonUtil.treeToValue(resultNode, returnType); completableFuture.complete(result); } catch (JsonProcessingException ex) { completableFuture.completeExceptionally( new JsonRpcResultTypeMismatchException( "Failed to convert result " + resultNode + " to type " + returnType, ex) ); } } } @Override public void shutdown() { if (isActive.getAndSet(false)) { transporter.close(); callContexts.forEach((key, callContext) -> callContext.getCompletableFuture() .completeExceptionally( new JsonRpcConnectionClosedException("Connection for this client is closed.") ) ); callContexts.clear(); LOGGER.info("The client is shutdown."); } } private void throwExceptionIfNotActive() throws JsonRpcConnectionClosedException { if (!isActive.get()) { throw new JsonRpcConnectionClosedException("Connection for this client is closed."); } } private void sendRequest(JsonNode requst) throws JsonRpcTransportException { LOGGER.debug("Sending request {}", requst); transporter.send(requst); } private static class CallContext { private Class returnType; private CompletableFuture completableFuture = null; private ScheduledFuture timeoutFuture = null; CallContext(CompletableFuture completableFuture, Class returnType) { this.completableFuture = completableFuture; this.returnType = returnType; } CompletableFuture getCompletableFuture() { return completableFuture; } Class getReturnType() { return returnType; } ScheduledFuture getTimeoutFuture() { return timeoutFuture; } void setTimeoutFuture(ScheduledFuture timeoutFuture) { this.timeoutFuture = timeoutFuture; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy