Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.pipservices3.rpc.clients.RestClient Maven / Gradle / Ivy
Go to download
Networking and communication platform for Pip.Services in Java
package org.pipservices3.rpc.clients;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.*;
import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.pipservices3.commons.config.ConfigParams;
import org.pipservices3.commons.config.IConfigurable;
import org.pipservices3.commons.data.FilterParams;
import org.pipservices3.commons.data.PagingParams;
import org.pipservices3.commons.errors.*;
import org.pipservices3.commons.refer.IReferenceable;
import org.pipservices3.commons.refer.IReferences;
import org.pipservices3.commons.refer.ReferenceException;
import org.pipservices3.commons.run.IOpenable;
import org.pipservices3.components.connect.ConnectionParams;
import org.pipservices3.components.count.CompositeCounters;
import org.pipservices3.components.log.CompositeLogger;
import org.pipservices3.components.trace.CompositeTracer;
import org.pipservices3.rpc.connect.HttpConnectionResolver;
import org.pipservices3.rpc.services.InstrumentTiming;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.temporal.ChronoUnit;
/**
* Abstract client that calls remove endpoints using HTTP/REST protocol.
*
* ### Configuration parameters ###
*
* base_route: base route for remote URI
* connection(s):
*
* discovery_key: (optional) a key to retrieve the connection from IDiscovery
* protocol: connection protocol: http or https
* host: host name or IP address
* port: port number
* uri: resource URI or connection string with all parameters in it
*
* options:
*
* retries: number of retries (default: 3)
* connect_timeout: connection timeout in milliseconds (default: 10 sec)
* timeout: invocation timeout in milliseconds (default: 10 sec)
*
*
*
* ### References ###
*
* *:logger:*:*:1.0 (optional) ILogger components to pass log messages
* *:counters:*:*:1.0 (optional) ICounters components to pass collected measurements
* *:tracer:*:*:1.0 (optional) ITracer components to record traces
* *:discovery:*:*:1.0 (optional) IDiscovery services to resolve connection
*
*
* ### Example ###
*
* {@code
* class MyRestClient extends RestClient implements IMyClient {
* ...
*
* public MyData getData(String correlationId, String id) {
* Timing timing = this.instrument(correlationId, 'myclient.get_data');
* MyData result = this.execute(MyData.class, correlationId, HttpMethod.POST, "/get_data", new MyData(id));
* timing.endTiming();
* return result;
* }
* ...
* }
*
* MyRestClient client = new MyRestClient();
* client.configure(ConfigParams.fromTuples(
* "connection.protocol", "http",
* "connection.host", "localhost",
* "connection.port", 8080
* ));
*
* MyData data = client.getData("123", "1");
* ...
* }
*
*/
public class RestClient implements IOpenable, IConfigurable, IReferenceable {
private final static ConfigParams _defaultConfig = ConfigParams.fromTuples(
"connection.protocol", "http",
"connection.host", "0.0.0.0",
"connection.port", 3000,
"options.request_max_size", 1024 * 1024,
"options.connect_timeout", 10000,
"options.timeout", 10000,
"options.retries", 3,
"options.debug", true
);
/**
* The connection resolver.
*/
protected HttpConnectionResolver _connectionResolver = new HttpConnectionResolver();
/**
* The logger.
*/
protected CompositeLogger _logger = new CompositeLogger();
/**
* The performance counters.
*/
protected CompositeCounters _counters = new CompositeCounters();
/**
* The tracer.
*/
protected CompositeTracer _tracer = new CompositeTracer();
/**
* The configuration options.
*/
protected ConfigParams _options = new ConfigParams();
/**
* The base route.
*/
protected String _baseRoute;
/**
* The number of retries.
*/
protected int _retries = 1;
/**
* The connection timeout in milliseconds.
*/
protected long _connectTimeout = 10000;
/**
* The invocation timeout in milliseconds.
*/
protected long _timeout = 10000;
/**
* The default headers to be added to every request.
*/
protected MultivaluedMap _headers = new MultivaluedHashMap<>();
protected String _correlationIdLocation = "query";
/**
* The remote service uri which is calculated on open.
*/
protected String _url;
/**
* The HTTP client.
*/
protected Client _client;
/**
* Creates a new instance of the client.
*/
protected RestClient() {
this(null);
}
/**
* Creates a new instance of the client.
*
* @param baseRoute a base route for remote service.
*/
protected RestClient(String baseRoute) {
_baseRoute = baseRoute;
}
/**
* Client retry strategy
*/
private RetryPolicy _retryPolicy;
/**
* Configures component by passing configuration parameters.
*
* @param config configuration parameters to be set.
*/
@Override
public void configure(ConfigParams config) {
config = config.setDefaults(RestClient._defaultConfig);
this._connectionResolver.configure(config);
this._options = this._options.override(config.getSection("options"));
this._retries = config.getAsIntegerWithDefault("options.retries", this._retries);
this._connectTimeout = config.getAsLongWithDefault("options.connect_timeout", this._connectTimeout);
this._timeout = config.getAsLongWithDefault("options.timeout", this._timeout);
this._baseRoute = config.getAsStringWithDefault("base_route", this._baseRoute);
this._correlationIdLocation = config.getAsStringWithDefault("options.correlation_id_place", this._correlationIdLocation);
this._correlationIdLocation = config.getAsStringWithDefault("options.correlation_id", this._correlationIdLocation);
}
/**
* Sets references to dependent components.
*
* @param references references to locate the component dependencies.
* @throws ReferenceException when no found references.
*/
@Override
public void setReferences(IReferences references) throws ReferenceException {
_logger.setReferences(references);
_counters.setReferences(references);
_tracer.setReferences(references);
_connectionResolver.setReferences(references);
}
/**
* Adds instrumentation to log calls and measure call time. It returns a Timing
* object that is used to end the time measurement.
*
* @param correlationId (optional) transaction id to trace execution through
* call chain.
* @param name a method name.
* @return Timing object to end the time measurement.
*/
protected InstrumentTiming instrument(String correlationId, String name) {
this._logger.trace(correlationId, "Calling %s method", name);
this._counters.incrementOne(name + ".call_count");
var counterTiming = this._counters.beginTiming(name + ".call_time");
var traceTiming = this._tracer.beginTrace(correlationId, name, null);
return new InstrumentTiming(correlationId, name, "call",
this._logger, this._counters, counterTiming, traceTiming);
}
/**
* Checks if the component is opened.
*
* @return true if the component has been opened and false otherwise.
*/
@Override
public boolean isOpen() {
return _client != null;
}
/**
* Opens the component.
*
* @param correlationId (optional) transaction id to trace execution through
* call chain.
* @throws ApplicationException when error occured.
*/
@Override
public void open(String correlationId) throws ApplicationException {
// Skip if already opened
if (_client != null)
return;
ConnectionParams connection = _connectionResolver.resolve(correlationId);
String protocol = connection.getProtocolWithDefault("http");
String host = connection.getHost();
int port = connection.getPort();
_url = protocol + "://" + host + ":" + port;
ClientConfig clientConfig = new ClientConfig();
clientConfig.property(ClientProperties.CONNECT_TIMEOUT, _connectTimeout);
clientConfig.property(ClientProperties.READ_TIMEOUT, _timeout);
_retryPolicy = new RetryPolicy<>()
.withDelay(_timeout, _timeout * 10, ChronoUnit.MILLIS)
.withMaxRetries(_retries);
clientConfig.register(new JacksonFeature());
_client = ClientBuilder.newClient(clientConfig);
_logger.debug(correlationId, "Connected via REST to %s", _url);
}
/**
* Closes component and frees used resources.
*
* @param correlationId (optional) transaction id to trace execution through
* call chain.
*/
public void close(String correlationId) {
if (_client == null)
return;
_client.close();
_client = null;
_logger.debug(correlationId, "Disconnected from %s", _url);
_url = null;
}
private URI createRequestUri(String route) {
StringBuilder builder = new StringBuilder(_url);
if (_baseRoute != null && _baseRoute.trim().length() > 0) {
if (_baseRoute.charAt(0) != '/')
builder.append('/');
builder.append(_baseRoute);
}
if (route.charAt(0) != '/')
builder.append('/');
builder.append(route);
String uri = builder.toString();
return UriBuilder.fromUri(uri).build();
}
private String addQueryParameter(String query, String name, String value) {
name = URLEncoder.encode(name, StandardCharsets.UTF_8);
value = value != null ? URLEncoder.encode(value, StandardCharsets.UTF_8) : "";
int pos = query.indexOf('?');
String path = pos >= 0 ? query.substring(0, pos) : query;
String parameters = pos >= 0 ? query.substring(pos) : "";
return path + "?" + (parameters.equals("") ? "" : "&") + name + "=" + value;
}
/**
* Adds a correlation id (correlation_id) to invocation parameter map.
*
* @param route invocation parameters.
* @param correlationId (optional) a correlation id to be added.
* @return invocation parameters with added correlation id.
*/
protected String addCorrelationId(String route, String correlationId) {
return addQueryParameter(route, "correlation_id", correlationId);
}
/**
* Adds filter parameters (with the same name as they defined) to invocation
* parameter map.
*
* @param route invocation parameters.
* @param filter (optional) filter parameters
* @return invocation parameters with added filter parameters.
*/
protected String addFilterParams(String route, FilterParams filter) {
for (String key : filter.keySet()) {
route = addQueryParameter(route, key, filter.get(key));
}
return route;
}
/**
* Adds paging parameters (skip, take, total) to invocation parameter map.
*
* @param route invocation parameters.
* @param paging (optional) paging parameters
* @return invocation parameters with added paging parameters.
*/
protected String addPagingParams(String route, PagingParams paging) {
if (paging.getSkip() != null)
route = addQueryParameter(route, "skip", paging.getSkip().toString());
if (paging.getTake() != null)
route = addQueryParameter(route, "take", paging.getTake().toString());
if (paging.hasTotal())
route = addQueryParameter(route, "total", paging.getTake().toString());
return route;
}
protected Response executeRequest(String correlationId, String method, URI uri, String mediaType, Entity> body)
throws ApplicationException {
if (_client == null) {
throw new InvalidStateException(correlationId, "NOT_OPENED", "Client is not opened");
}
Response response = null;
response = Failsafe.with(_retryPolicy).get(
() -> _client.target(uri).request(mediaType).headers(_headers).method(method, body));
if (response == null) {
throw new UnknownException(correlationId, "NO_RESPONSE",
"Unable to get a result from " + method + " " + uri);
}
if (response.getStatus() >= 400) {
ErrorDescription errorObject = null;
try {
errorObject = response.readEntity(ErrorDescription.class);
} catch (Exception ex) {
// Todo: This may not work as expected. Find another way to get content string
// String res = response.readEntity(String.class);
Object res = response.readEntity(Object.class);
String responseContent = res.toString();
throw new UnknownException(correlationId, "UNKNOWN_ERROR", responseContent);
}
if (errorObject != null)
throw ApplicationExceptionFactory.create(errorObject);
}
return response;
}
private Response executeJsonRequest(String correlationId, String method, String route, Object requestEntity)
throws ApplicationException {
route = addCorrelationId(route, correlationId);
URI uri = createRequestUri(route);
Entity> body = Entity.entity(requestEntity, MediaType.APPLICATION_JSON);
return executeRequest(correlationId, method, uri, MediaType.APPLICATION_JSON, body);
}
/**
* Executes a remote method via HTTP/REST protocol.
*
* @param type the class type of data.
* @param correlationId (optional) transaction id to trace execution through
* call chain.
* @param method HTTP method: "get", "head", "post", "put", "delete"
* @param route a command route. Base route will be added to this route
* @param requestEntity request body object.
* @return result object.
* @throws ApplicationException when error occured.
*/
protected T call(Class type, String correlationId, String method, String route, Object requestEntity)
throws ApplicationException {
try (Response response = executeJsonRequest(correlationId, method, route, requestEntity)) {
return response.readEntity(type);
}
// catch (Throwable ex) {
// throw new InvocationException(correlationId, "SERIALIZATION_FAILED", "Failed to deserialize HTTP response")
// .withCause(ex);
// }
}
/**
* Executes a remote method via HTTP/REST protocol.
*
* @param type the generic class type of data.
* @param correlationId (optional) transaction id to trace execution through
* call chain.
* @param method HTTP method: "get", "head", "post", "put", "delete"
* @param route a command route. Base route will be added to this route
* @param requestEntity request body object.
* @return result object.
* @throws ApplicationException when error occured.
*/
protected T call(GenericType type, String correlationId, String method, String route,
Object requestEntity) throws ApplicationException {
try (Response response = executeJsonRequest(correlationId, method, route, requestEntity)) {
return response.readEntity(type);
}
// } catch (Throwable ex) {
// throw new InvocationException(correlationId, "SERIALIZATION_FAILED", "Failed to deserialize HTTP response")
// .withCause(ex);
// }
}
}