BaseService.java
package org.vlaskin.moexiss.service;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.Validate;
import org.vlaskin.moexiss.InternalApi;
import org.vlaskin.moexiss.http.ApacheMoexHttpTransport;
import org.vlaskin.moexiss.http.MoexHttpTransport;
import org.vlaskin.moexiss.response.Response;
import org.vlaskin.moexiss.response.ResponseDeserializer;
import org.vlaskin.moexiss.response.field.FieldResponse;
import org.vlaskin.moexiss.response.field.FieldResponseDeserializer;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* Базовый класс сервисов MOEX ISS, отвечающий за запросы, кодирование и разбор JSON.
*
* @hidden
*/
@Slf4j
@InternalApi
public abstract class BaseService
{
/** Официальный базовый адрес MOEX ISS. */
public static final String DEFAULT_BASE_URL = "https://iss.moex.com";
protected final String baseUrl;
protected final MoexHttpTransport httpTransport;
protected final Gson gson;
/** Создаёт сервис для официального адреса MOEX ISS. */
protected BaseService()
{
this(DEFAULT_BASE_URL, new ApacheMoexHttpTransport());
}
protected BaseService(String baseUrl, MoexHttpTransport httpTransport)
{
this.baseUrl = removeTrailingSlash(
Validate.notBlank(baseUrl, "Base URL must not be blank").strip());
Validate.notBlank(this.baseUrl,
"Base URL must not contain only '/' characters");
this.httpTransport = Validate.notNull(httpTransport,
"HTTP transport must not be null");
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Response.class, new ResponseDeserializer());
gsonBuilder.registerTypeAdapter(FieldResponse.class, new FieldResponseDeserializer());
gson = gsonBuilder.create();
}
protected String get(CharSequence request) throws IOException
{
return httpTransport.get(request.toString());
}
protected <T> T getAndParseResponse(CharSequence request, Class<T> responseType)
throws IOException
{
String response = get(request);
log.debug("Response received: {} characters", response == null ? 0 : response.length());
return parseResponse(response, responseType);
}
protected <T> T parseResponse(String response, Class<T> responseType) throws IOException
{
if (response == null || response.isBlank())
throw new IOException("MOEX ISS returned an empty response");
try
{
T result = gson.fromJson(response, responseType);
if (result == null)
throw new IOException("MOEX ISS returned an empty response");
return result;
}
catch (JsonParseException exception)
{
throw new IOException("Failed to parse MOEX ISS response", exception);
}
}
protected static void pasteBasicRequestParams(StringBuilder requestBuilder, String... only)
{
requestBuilder.append("?iss.meta=on");
if (only != null && only.length > 0)
appendQueryParameter(requestBuilder, "iss.only", List.of(only));
}
protected static String encodeQueryParameter(Object value)
{
return URLEncoder.encode(value.toString(), StandardCharsets.UTF_8);
}
protected static String encodePathSegment(Object value)
{
return encodeQueryParameter(value).replace("+", "%20");
}
protected static void appendQueryParameter(StringBuilder requestBuilder, String name, Object value)
{
requestBuilder.append('&').append(name).append('=').append(encodeQueryParameter(value));
}
protected static void appendQueryParameter(StringBuilder requestBuilder, String name,
Collection<?> values)
{
String encodedValues = values.stream()
.map(BaseService::encodeQueryParameter)
.collect(Collectors.joining(","));
requestBuilder.append('&').append(name).append('=').append(encodedValues);
}
protected static <T> T requireParams(T params)
{
return Validate.notNull(params, "Request parameters must not be null");
}
protected static <T> T requireParameter(T value, String name)
{
return Validate.notNull(value, "Parameter " + name + " must not be null");
}
protected static String requireTextParameter(String value, String name)
{
return Validate.notBlank(value, "Parameter " + name + " must not be blank");
}
protected static void requireNonNegative(int value, String name)
{
Validate.isTrue(value >= 0, "Parameter " + name + " must not be negative");
}
protected static void requirePositive(int value, String name)
{
Validate.isTrue(value > 0, "Parameter " + name + " must be greater than zero");
}
protected static int calculateStartIndex(int pageIndex, int limit)
{
requireNonNegative(pageIndex, "pageIndex");
requirePositive(limit, "limit");
try
{
return Math.multiplyExact(pageIndex, limit);
}
catch (ArithmeticException exception)
{
throw new IllegalArgumentException(
"Product of pageIndex and limit exceeds the supported range",
exception);
}
}
private static String removeTrailingSlash(String url)
{
int end = url.length();
while (end > 0 && url.charAt(end - 1) == '/')
end--;
return url.substring(0, end);
}
}