package com.googlecode.jsonrpc4j.spring.rest;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.googlecode.jsonrpc4j.DefaultHttpStatusCodeProvider;
import com.googlecode.jsonrpc4j.IJsonRpcClient;
import com.googlecode.jsonrpc4j.JsonRpcClient;
import com.googlecode.jsonrpc4j.JsonRpcClientException;
import org.springframework.http.HttpEntity;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.lang.reflect.Type;
import java.net.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.web.client.ResponseErrorHandler;
@SuppressWarnings({"unused", "WeakerAccess"})
public class JsonRpcRestClient extends JsonRpcClient implements IJsonRpcClient {
private final AtomicReference<URL> serviceUrl = new AtomicReference<>();
private final RestTemplate restTemplate;
private final Map<String, String> headers = new HashMap<>();
private final SslClientHttpRequestFactory requestFactory;
public JsonRpcRestClient(URL serviceUrl) {
this(serviceUrl, new ObjectMapper());
}
private JsonRpcRestClient(URL serviceUrl, ObjectMapper mapper) {
this(serviceUrl, mapper, null, new HashMap<String, String>());
}
@SuppressWarnings("WeakerAccess")
public JsonRpcRestClient(URL serviceUrl, ObjectMapper mapper, RestTemplate restTemplate, Map<String, String> headers) {
super(mapper);
this.requestFactory = restTemplate != null ? null : new SslClientHttpRequestFactory();
this.restTemplate = restTemplate != null ? restTemplate : new RestTemplate(this.requestFactory);
this.serviceUrl.set(serviceUrl);
if (headers != null) {
this.headers.putAll(headers);
}
// Now check RestTemplate contains required converters
this.initRestTemplate();
}
/**
* Check RestTemplate contains required converters
*/
private void initRestTemplate() {
boolean isContainsConverter = false;
for (HttpMessageConverter<?> httpMessageConverter : this.restTemplate.getMessageConverters()) {
if (MappingJacksonRPC2HttpMessageConverter.class.isAssignableFrom(httpMessageConverter.getClass())) {
isContainsConverter = true;
break;
}
}
if (!isContainsConverter) {
final MappingJacksonRPC2HttpMessageConverter messageConverter = new MappingJacksonRPC2HttpMessageConverter();
messageConverter.setObjectMapper(this.getObjectMapper());
final List<HttpMessageConverter<?>> restMessageConverters = new ArrayList<>();
restMessageConverters.addAll(this.restTemplate.getMessageConverters());
// Place JSON-RPC converter on the first place!
restMessageConverters.add(0, messageConverter);
this.restTemplate.setMessageConverters(restMessageConverters);
}
// use specific JSON-RPC error handler if it has not been changed to custom
if (restTemplate.getErrorHandler() instanceof org.springframework.web.client.DefaultResponseErrorHandler)
restTemplate.setErrorHandler(JsonRpcResponseErrorHandler.INSTANCE);
}
public JsonRpcRestClient(URL serviceUrl, Map<String, String> headers) {
this(serviceUrl, new ObjectMapper(), headers);
}
private JsonRpcRestClient(URL serviceUrl, ObjectMapper mapper, Map<String, String> headers) {
this(serviceUrl, mapper, null, headers);
}
public JsonRpcRestClient(URL serviceUrl, RestTemplate restTemplate) {
this(serviceUrl, new ObjectMapper(), restTemplate, null);
}
/**
* @param connectionProxy the connectionProxy to set
*/
public void setConnectionProxy(Proxy connectionProxy) {
this.getRequestFactory().setProxy(connectionProxy);
}
private SslClientHttpRequestFactory getRequestFactory() {
if (this.requestFactory == null) {
throw new IllegalStateException("Used external RequestTemplate instance");
}
return requestFactory;
}
/**
* @param connectionTimeoutMillis the connectionTimeoutMillis to set
*/
public void setConnectionTimeoutMillis(int connectionTimeoutMillis) {
this.getRequestFactory().setConnectTimeout(connectionTimeoutMillis);
}
/**
* @param readTimeoutMillis the readTimeoutMillis to set
*/
public void setReadTimeoutMillis(int readTimeoutMillis) {
this.getRequestFactory().setReadTimeout(readTimeoutMillis);
}
/**
* @return the headers
*/
public Map<String, String> getHeaders() {
return Collections.unmodifiableMap(headers);
}
/**
* @param headers the headers to set
*/
public void setHeaders(Map<String, String> headers) {
this.headers.clear();
this.headers.putAll(headers);
}
/**
* @param sslContext the sslContext to set
*/
public void setSslContext(SSLContext sslContext) {
if (sslContext != null) {
this.getRequestFactory().setSslContext(sslContext);
}
}
/**
* @param hostNameVerifier the hostNameVerifier to set
*/
public void setHostNameVerifier(HostnameVerifier hostNameVerifier) {
if (hostNameVerifier != null) {
this.getRequestFactory().setHostNameVerifier(hostNameVerifier);
}
}
public URL getServiceUrl() {
return serviceUrl.get();
}
public void setServiceUrl(URL serviceUrl) {
this.serviceUrl.set(serviceUrl);
}
/**
* {@inheritDoc}
*/
@Override
public void invoke(String methodName, Object argument) throws Throwable {
invoke(methodName, argument, null, new HashMap<String, String>());
}
/**
* {@inheritDoc}
*/
@Override
public Object invoke(String methodName, Object argument, Type returnType) throws Throwable {
return invoke(methodName, argument, returnType, new HashMap<String, String>());
}
/**
* {@inheritDoc}
*/
@Override
public Object invoke(String methodName, Object argument, Type returnType, Map<String, String> extraHeaders) throws Throwable {
final ObjectNode request = super.createRequest(methodName, argument);
final MultiValueMap<String, String> httpHeaders = new LinkedMultiValueMap<>();
for (Map.Entry<String, String> entry : this.headers.entrySet()) {
httpHeaders.add(entry.getKey(), entry.getValue());
}
if (extraHeaders != null) {
for (Map.Entry<String, String> entry : extraHeaders.entrySet()) {
httpHeaders.add(entry.getKey(), entry.getValue());
}
}
final HttpEntity<ObjectNode> requestHttpEntity = new HttpEntity<>(request, httpHeaders);
JsonNode response;
try {
response = this.restTemplate.postForObject(serviceUrl.get().toExternalForm(), requestHttpEntity, ObjectNode.class);
} catch (HttpStatusCodeException httpStatusCodeException) {
logger.error("HTTP Error code={} status={}\nresponse={}"
, httpStatusCodeException.getStatusCode().value()
, httpStatusCodeException.getStatusText()
, httpStatusCodeException.getResponseBodyAsString()
);
Integer jsonErrorCode = DefaultHttpStatusCodeProvider.INSTANCE.getJsonRpcCode(httpStatusCodeException.getStatusCode().value());
if (jsonErrorCode == null)
jsonErrorCode = httpStatusCodeException.getStatusCode().value();
throw new JsonRpcClientException(jsonErrorCode, httpStatusCodeException.getStatusText(), null);
} catch (HttpMessageConversionException httpMessageConversionException) {
logger.error("Can not convert (request/response)", httpMessageConversionException);
throw new JsonRpcClientException(0, "Invalid JSON-RPC response", null);
}
return this.readResponse(returnType, response);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <T> T invoke(String methodName, Object argument, Class<T> clazz) throws Throwable {
return (T) invoke(methodName, argument, Type.class.cast(clazz));
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <T> T invoke(String methodName, Object argument, Class<T> clazz, Map<String, String> extraHeaders) throws Throwable {
return (T) invoke(methodName, argument, Type.class.cast(clazz), extraHeaders);
}
}