/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.core.jsonrpc.commons; import org.slf4j.Logger; import javax.inject.Inject; import javax.inject.Singleton; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; /** * Dispatches JSON RPC responses */ @Singleton public class ResponseDispatcher { private final static Logger LOGGER = getLogger(ResponseDispatcher.class); private final JsonRpcComposer composer; private final Map<String, JsonRpcPromise> promises = new ConcurrentHashMap<>(); private final Map<String, Class<?>> rClasses = new ConcurrentHashMap<>(); @Inject public ResponseDispatcher(JsonRpcComposer composer) { this.composer = composer; } private static void checkArguments(String endpointId, String requestId, Class<?> rClass, JsonRpcPromise success) { checkNotNull(endpointId, "Endpoint ID must not be null"); checkArgument(!endpointId.isEmpty(), "Endpoint ID must not be empty"); checkNotNull(requestId, "Request ID must not be null"); checkArgument(!requestId.isEmpty(), "Request ID must not be empty"); checkNotNull(rClass, "Result class must not be null"); checkNotNull(success, "Json rpc promise must not be null"); } private static String combine(String endpointId, String requestId) { return endpointId + '@' + requestId; } @SuppressWarnings("unchecked") private static <T> T cast(Object object) { return (T)object; } private <R> void processOne(String endpointId, JsonRpcResult jsonRpcResult, Class<R> resultClass, BiConsumer<String, R> consumer) { LOGGER.debug("Result is a single object - processing single object..."); R result = composer.composeOne(jsonRpcResult, resultClass); consumer.accept(endpointId, result); } private <R> void processMany(String endpointId, JsonRpcResult jsonRpcResult, Class<R> resultClass, BiConsumer<String, List> consumer) { LOGGER.debug("Result is an array - processing array..."); List<R> result = composer.composeMany(jsonRpcResult, resultClass); consumer.accept(endpointId, result); } public void dispatch(String endpointId, JsonRpcResponse response) { checkNotNull(endpointId, "Endpoint ID name must not be null"); checkArgument(!endpointId.isEmpty(), "Endpoint ID name must not be empty"); checkNotNull(response, "Response name must not be null"); LOGGER.debug("Dispatching a response: " + response + ", from endpoint: " + endpointId); String responseId = response.getId(); if (responseId == null) { LOGGER.debug("Response ID is not defined, skipping..."); return; } LOGGER.debug("Fetching response ID: " + responseId); String key = combine(endpointId, responseId); LOGGER.debug("Generating key: " + key); Class<?> rClass = rClasses.remove(key); LOGGER.debug("Fetching result class:" + rClass); if (response.hasResult()) { processResult(endpointId, response, key, rClass); } else if (response.hasError()) { processError(endpointId, response, key); } else { LOGGER.error("Received incorrect response: no error, no result"); } } private void processError(String endpointId, JsonRpcResponse response, String key) { LOGGER.debug("Response has error. Proceeding..."); JsonRpcError error = response.getError(); JsonRpcPromise<JsonRpcError> jsonRpcPromise = cast(promises.remove(key)); BiConsumer<String, JsonRpcError> consumer = jsonRpcPromise.getFailureConsumer(); if (consumer != null) { LOGGER.debug("Failure consumer is found, accepting..."); consumer.accept(endpointId, error); } else { LOGGER.debug("Reject function is not found, skipping"); } } private void processResult(String endpointId, JsonRpcResponse response, String key, Class<?> rClass) { LOGGER.debug("Response has result. Proceeding..."); JsonRpcResult result = response.getResult(); if (result.isSingle()) { processOne(endpointId, result, rClass, cast(promises.remove(key).getSuccessConsumer())); } else { processMany(endpointId, result, rClass, cast(promises.remove(key).getSuccessConsumer())); } } public synchronized <R> JsonRpcPromise<R> registerPromiseOfOne(String endpointId, String requestId, Class<R> rClass) { return cast(registerInternal(endpointId, requestId, rClass, new JsonRpcPromise<R>())); } public synchronized <R> JsonRpcPromise<List<R>> registerPromiseOfMany(String endpointId, String requestId, Class<R> rClass) { return cast(registerInternal(endpointId, requestId, rClass, new JsonRpcPromise<List<R>>())); } private <R> JsonRpcPromise registerInternal(String endpointId, String requestId, Class<R> rClass, JsonRpcPromise promise) { checkArguments(endpointId, requestId, rClass, promise); String key = combine(endpointId, requestId); promises.put(key, promise); rClasses.put(key, rClass); return promise; } }