meta: example for money market security and test

This commit is contained in:
ialbert 2022-11-03 16:21:24 +03:00
parent 0ed21584c1
commit fac3f80184
11 changed files with 392 additions and 43 deletions

View file

@ -1,9 +1,16 @@
package ru.spcex.clearing.backendapi.config;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import ru.spcex.clearing.backendapi.meta.MetaServer;
import java.io.IOException;
import java.nio.file.Files;
@ -18,4 +25,15 @@ public class MetaConfiguration {
if (!meta.isReadable()) throw new IllegalStateException("cannot read meta.json from spring.config.location");
return Files.readString(meta.getFile().toPath());
}
@Autowired
@Bean
public MetaServer metaServer(@Qualifier("metaJson") String metaJson) throws JsonProcessingException {
ObjectMapper jsonObjectMapper = new ObjectMapper();
jsonObjectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
jsonObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MetaServer metaServer = jsonObjectMapper.readValue(metaJson, MetaServer.class);
metaServer.initAndValidate();
return metaServer;
}
}

View file

@ -16,6 +16,7 @@ import ru.spcex.clearing.backendapi.controller.request.cud.securities.MoneyMarke
import ru.spcex.clearing.backendapi.controller.response.BasicSpcexResponse;
import ru.spcex.clearing.backendapi.controller.response.cud.CudResponse;
import ru.spcex.clearing.backendapi.controller.response.entity.securities.MoneyMarketSecurityBackendGetAll;
import ru.spcex.clearing.backendapi.meta.GetResponseFactory;
import ru.spcex.clearing.backendapi.service.IOperator;
import ru.spcex.clearing.backendapi.service.IStateLoader;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
@ -69,6 +70,9 @@ public class CudMoneyMarketSecurityController extends AbstractQueueController {
return processRequest(Consts.DESTINATION_MONEY_MARKET_SECURITY_DELETE, deleteAction);
}
@Autowired
private GetResponseFactory responseFactory;
@ApiOperation(value = "get all money market securities.")
@ApiResponses(value = {@ApiResponse(code = 200, message = "OK", response = MoneyMarketSecurityBackendGetAll.class)})
@RequestMapping(method = RequestMethod.GET)
@ -76,7 +80,7 @@ public class CudMoneyMarketSecurityController extends AbstractQueueController {
public MoneyMarketSecurityBackendGetAll getAll() {
Collection<MoneyMarketSecurity> all = stateLoader.getAll(IMDGDistributedNames.Map_MoneyMarketSecurity, MoneyMarketSecurity.class);
MoneyMarketSecurityBackendGetAll response = new MoneyMarketSecurityBackendGetAll();
response.fromEntity(all);
response.fromEntity(responseFactory.responseFromObjectCollection(all));
return response;
}
}

View file

@ -3,12 +3,12 @@ package ru.spcex.clearing.backendapi.controller.response.entity.securities;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import ru.clearing.classes.statics.data.misc.MoneyMarketSecurity;
import ru.spcex.clearing.backendapi.controller.response.BasicSpcexResponse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ApiModel(description = "Ответ при получении объектов MoneyMarketSecurity.")
public class MoneyMarketSecurityBackendGetAll extends BasicSpcexResponse {
@ -18,13 +18,13 @@ public class MoneyMarketSecurityBackendGetAll extends BasicSpcexResponse {
private MoneyMarketSecurityBackendPayload payload = new MoneyMarketSecurityBackendPayload();
private static class MoneyMarketSecurityBackendPayload {
private List<MoneyMarketSecurityBackendGetFields> items = new ArrayList<>();
private List<Map<String, Object>> items = new ArrayList<>();
public List<MoneyMarketSecurityBackendGetFields> getItems() {
public List<Map<String, Object>> getItems() {
return items;
}
public void setItems(List<MoneyMarketSecurityBackendGetFields> items) {
public void setItems(List<Map<String, Object>> items) {
this.items = items;
}
}
@ -37,22 +37,10 @@ public class MoneyMarketSecurityBackendGetAll extends BasicSpcexResponse {
this.payload = payload;
}
public void fromEntity(Collection<MoneyMarketSecurity> moneyMarketSecurities) {
public void fromEntity(Collection<Map<String, Object>> moneyMarketSecurities) {
var payload = this.getPayload();
for (MoneyMarketSecurity moneyMarketSecurity : moneyMarketSecurities) {
var singleItem = new MoneyMarketSecurityBackendGetFields();
singleItem.setId(moneyMarketSecurity.getId());
singleItem.setSecurityId(moneyMarketSecurity.getSecurityId());
singleItem.setDescription(moneyMarketSecurity.getDescription());
singleItem.setStartDate(moneyMarketSecurity.getStartDate());
singleItem.setEndDate(moneyMarketSecurity.getEndDate());
singleItem.setNominalValue(moneyMarketSecurity.getNominalValue());
singleItem.setNominalCurrency(moneyMarketSecurity.getNominalCurrency());
singleItem.setInstrumentType(moneyMarketSecurity.getInstrumentType());
singleItem.setFullName(moneyMarketSecurity.getFullName());
singleItem.setSecuritySymbol(moneyMarketSecurity.getSecuritySymbol());
payload.getItems().add(singleItem);
for (Map<String, Object> moneyMarketSecurity : moneyMarketSecurities) {
payload.getItems().add(moneyMarketSecurity);
}
}
}

View file

@ -0,0 +1,59 @@
package ru.spcex.clearing.backendapi.controller.response.entity.securities;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import ru.clearing.classes.statics.data.misc.MoneyMarketSecurity;
import ru.spcex.clearing.backendapi.controller.response.BasicSpcexResponse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Deprecated
@ApiModel(description = "Ответ при получении объектов MoneyMarketSecurity.")
public class MoneyMarketSecurityBackendGetAllOld extends BasicSpcexResponse {
@JsonProperty
@ApiModelProperty(value = "Полезная нагрузка")
private MoneyMarketSecurityBackendPayload payload = new MoneyMarketSecurityBackendPayload();
private static class MoneyMarketSecurityBackendPayload {
private List<MoneyMarketSecurityBackendGetFields> items = new ArrayList<>();
public List<MoneyMarketSecurityBackendGetFields> getItems() {
return items;
}
public void setItems(List<MoneyMarketSecurityBackendGetFields> items) {
this.items = items;
}
}
public MoneyMarketSecurityBackendPayload getPayload() {
return payload;
}
public void setPayload(MoneyMarketSecurityBackendPayload payload) {
this.payload = payload;
}
public void fromEntity(Collection<MoneyMarketSecurity> moneyMarketSecurities) {
var payload = this.getPayload();
for (MoneyMarketSecurity moneyMarketSecurity : moneyMarketSecurities) {
var singleItem = new MoneyMarketSecurityBackendGetFields();
singleItem.setId(moneyMarketSecurity.getId());
singleItem.setSecurityId(moneyMarketSecurity.getSecurityId());
singleItem.setDescription(moneyMarketSecurity.getDescription());
singleItem.setStartDate(moneyMarketSecurity.getStartDate());
singleItem.setEndDate(moneyMarketSecurity.getEndDate());
singleItem.setNominalValue(moneyMarketSecurity.getNominalValue());
singleItem.setNominalCurrency(moneyMarketSecurity.getNominalCurrency());
singleItem.setInstrumentType(moneyMarketSecurity.getInstrumentType());
singleItem.setFullName(moneyMarketSecurity.getFullName());
singleItem.setSecuritySymbol(moneyMarketSecurity.getSecuritySymbol());
payload.getItems().add(singleItem);
}
}
}

View file

@ -0,0 +1,17 @@
package ru.spcex.clearing.backendapi.meta;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClearingFieldExtracted extends FieldExtracted {
static final Logger log = LoggerFactory.getLogger(ClearingFieldExtracted.class);
public ClearingFieldExtracted(ActionField field) {
super(field);
}
@Override
public Object getObjectRef(Object o) {
throw new IllegalStateException("unsupported operation");
}
}

View file

@ -0,0 +1,14 @@
package ru.spcex.clearing.backendapi.meta;
import java.util.List;
public class ClearingObjectExtracted extends ObjectExtracted {
public ClearingObjectExtracted(String clazz, List<ActionField> actionFields) {
super(clazz, actionFields);
}
@Override
public FieldExtracted getImplFieldExtracted(ActionField field) {
return new ClearingFieldExtracted(field);
}
}

View file

@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.util.*;
public abstract class FieldExtracted {
@ -58,6 +59,7 @@ public abstract class FieldExtracted {
break;
}
//fixme somehow externalize custom serialization for type
// тут нужно вернуть список id объектов а не список самих объектов для случая
// - add(r, "addresseeId", o.getAddressee() == null ? null : o.getAddressee().stream().map(OtcObjectBase::getId).collect(Collectors.toSet()), fieldFilter);
if (result instanceof Set && ((Set) result).size() > 0) {
@ -70,6 +72,8 @@ public abstract class FieldExtracted {
result = t;
} catch (Throwable ignored) {// вернем сами объекты
}
} else if (result instanceof LocalDate) {
result = result.toString();
}
return result;
}

View file

@ -0,0 +1,125 @@
package ru.spcex.clearing.backendapi.meta;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@Component
public class GetResponseFactory {
private final Logger log = LoggerFactory.getLogger(getClass());
private final MetaServer meta;
@Autowired
public GetResponseFactory(MetaServer meta) {
this.meta = meta;
}
public Collection<Map<String, Object>> responseFromObjectCollection(Collection<?> o) {
return o.stream()
.map(this::responseFromObject)
.collect(Collectors.toList());
}
public Map<String, Object> responseFromObject(Object o) {
ObjectExtracted objExtr = meta.getObjectsExtractedByClazz().get(o.getClass().getCanonicalName());
if (objExtr == null) {
throw new RuntimeException("cannot find object in meta for " + o.getClass().getCanonicalName());
}
Map<String, Object> r = new LinkedHashMap<>();
FieldExtracted currentField = null;
try {
for (FieldExtracted field : objExtr.getFields()) {
if (field.getField().isVirtual() != null && field.getField().isVirtual()) {
continue;
}
currentField = field;
// if (fieldsToAdd.contains(field.getField().getCode())) {
try {
add(r, field.getField().getCode(), field.extractValue(o));
} catch (Throwable e) {
log.warn(ExceptionUtils.getStackTrace(
new RuntimeException(String.format("Can't extract value for field='%s' of %s(%s): %s -> %s\n%s",
field.getField().getCode(),
o.getClass().getSimpleName(), objExtr.getClassName(),
e.getClass().getSimpleName(), e.getLocalizedMessage(),
currentField.toString()
)))
);
}
// }
}
} catch (Exception e) {
log.error(ExceptionUtils.getStackTrace(e));
return null;
}
return r;
}
private void add(Map<String, Object> response, String name, Object value) {
Object data;
if (value instanceof Date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+03:00");
data = sdf.format(value);
} else if (value instanceof Instant) {
data = dateTimeFormatter.format((Instant) value);
} else {
data = value;
}
response.put(name, data);
}
private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss+03:00")
.withLocale(Locale.US)
.withZone(ZoneId.of("Europe/Moscow"));
// public Map<String, Object> newX(Object o, Collection<String> fieldFilter, String destination) {
// ObjectExtracted targetClazz = getTargetClazz(o, destination);
// if (targetClazz == null)
// throw new FrontendException(String.format("Unknown response class: %s", o.getClass().getName()));
// Collection<String> fieldsToAdd = getFilteredFields(targetClazz, fieldFilter);
// Map<String, Object> r = new LinkedHashMap<>();
// FieldExtracted currentField = null;
// try {
// for (FieldExtracted field : targetClazz.getFields()) {
// if (field.getField().isVirtual() != null && field.getField().isVirtual()) {
// continue;
// }
// currentField = field;
// if (fieldsToAdd.contains(field.getField().getCode())) {
// try {
// add(r, field.getField().getCode(), field.extractValue(o));
// } catch (Throwable e) {
// log.warn(ExceptionUtils.getStackTrace(
// new FrontendException(String.format("Can't extract value for field='%s' of %s(%s): %s -> %s\n%s",
// field.getField().getCode(),
// o.getClass().getSimpleName(), targetClazz.getClazz().getSimpleName(),
// e.getClass().getSimpleName(), e.getLocalizedMessage(),
// JsonHelper.writeAnyClassToLog(currentField)
// )))
// );
// }
// }
// }
// } catch (FrontendException e) {
// throw e;
// } catch (Throwable e) {
// throw new FrontendException(String.format("Can't get field of %s(%s): %s -> %s\n%s",
// o.getClass().getSimpleName(), targetClazz.getClazz().getSimpleName(),
// e.getClass().getSimpleName(), e.getLocalizedMessage(),
// JsonHelper.writeAnyClassToLog(currentField)
// ));
// }
// return r;
// }
}

View file

@ -9,7 +9,7 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public abstract class MetaServer extends MetaBase {
public class MetaServer extends MetaBase {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private Collection<ObjectExtracted> objectsExtracted;
@ -18,8 +18,11 @@ public abstract class MetaServer extends MetaBase {
private Map<String, ObjectExtracted> actionObjectsExtracted;
private Map<String, ObjectElement> objectElementByDestination;
protected abstract ObjectExtracted getImplInstanceObjectExtracted(String clazz, List<ActionField> actionFields);
protected ObjectExtracted getImplInstanceObjectExtracted(String clazz, List<ActionField> actionFields) {
return new ClearingObjectExtracted(clazz, actionFields);
}
private int classNameMock = 1;
@SuppressWarnings("Duplicates")
public void initAndValidate() throws ClearingMetaServerValidationException, OtcMetaServerGetterNotFoundException {
if (objectsExtracted == null) {
@ -47,11 +50,17 @@ public abstract class MetaServer extends MetaBase {
try {
oe = getImplInstanceObjectExtracted(objectElement.getClazz(), objectElement.getFields());
} catch (Throwable e) {
log.warn("META SERVER >>> Класс {} не найден!", objects.get(key).getClazz());
log.warn("META SERVER >>> Класс {} для {} не найден!", objects.get(key).getClazz(), key);
continue;
}
objectsExtracted.add(oe);
objectsExtractedByClazz.put(oe.getClassName(), oe);
if (oe.getClassName() != null) {
ObjectExtracted previous = objectsExtractedByClazz.put(oe.getClassName(), oe);
if (previous != null) {
log.warn("!!! meta contains duplicate classes {}", oe.getClassName());
}
} else
objectsExtractedByClazz.put(String.valueOf(classNameMock++), oe);
if (objectElement.getSubscription() != null && objectElement.getSubscription().destination != null) {
objectsExtractedByDestination.put(objectElement.getSubscription().destination, oe);
}
@ -93,25 +102,24 @@ public abstract class MetaServer extends MetaBase {
throw new ClearingMetaServerValidationException(e.getLocalizedMessage());
}
}
for (ObjectExtracted o : this.actionObjectsExtracted.values()) {
try {
if (!RfHelper.isAbstract(o.getClazz())) {
try {
Object instance = newInstance(o.getClassName());
// создается успешно.
} catch (Throwable e) {
log.warn("META SERVER ACTION >>> Не удается создать класс {} !", o.getClassName());
}
}
validateGetterForObject(o);
} catch (OtcMetaServerGetterNotFoundException e) {
throw e;
} catch (Throwable e) {
throw new ClearingMetaServerValidationException(e.getLocalizedMessage());
}
}
//
// for (ObjectExtracted o : this.actionObjectsExtracted.values()) {
// try {
// if (!RfHelper.isAbstract(o.getClazz())) {
// try {
// Object instance = newInstance(o.getClassName());
// // создается успешно.
// } catch (Throwable e) {
// log.warn("META SERVER ACTION >>> Не удается создать класс {} !", o.getClassName());
// }
// }
// validateGetterForObject(o);
// } catch (OtcMetaServerGetterNotFoundException e) {
// throw e;
// } catch (Throwable e) {
// throw new ClearingMetaServerValidationException(e.getLocalizedMessage());
// }
// }
}
/**

View file

@ -0,0 +1,66 @@
package ru.spcex.clearing.backendapi.meta;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.*;
import java.util.stream.Collectors;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = GetResponseFactoryTestConfiguration.class)
public class GetResponseFactoryTest {
private final MetaServer meta;
@Autowired
public GetResponseFactoryTest(@Qualifier("metaJsonTest") MetaServer meta) {
this.meta = meta;
}
@Test
public void testAllObjectsFields() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Collection<ObjectElement> allMetaObjects = meta.getObjects().values();
Set<Map.Entry<String, ObjectElement>> allMetaObjects2 = meta.getObjects().entrySet();
Map<String, List<String>> error = new HashMap<>();
for (Map.Entry<String, ObjectElement> ent : allMetaObjects2) {
ObjectElement objectElement = ent.getValue();
String className = objectElement.getClazz();
Class<?> classByName = null;
if (className == null) {
error.computeIfAbsent(ent.getKey(), v -> new ArrayList<>()).add(ent.getKey() + " no class property");
continue;
}
try {
classByName = Class.forName(className);
} catch (ClassNotFoundException e) {
error.computeIfAbsent(ent.getKey(), v -> new ArrayList<>()).add(ent.getKey() + " cannot find class " + className);
continue;
}
Object object = classByName.newInstance();
ObjectExtracted objectExtracted = meta.getObjectsExtractedByClazz().get(className);
for (FieldExtracted fieldExtracted : objectExtracted.getFields()) {
try {
fieldExtracted.extractValue(object);
} catch (Exception e) {
error.computeIfAbsent(ent.getKey(), v -> new ArrayList<>()).add(
"memberName " + fieldExtracted.getMemberName() + "; code " + fieldExtracted.getField().getCode() + "; field " + fieldExtracted.getField().getField()
);
}
}
}
String errorIfPresent = error
.entrySet()
.stream()
.map(entry -> entry.getKey() + ": " + entry.getValue()
.stream()
.collect(Collectors.joining(" |\n", "[", "]")))
.collect(Collectors.joining("\n\n\n"));
Assertions.assertEquals(0, error.size(), errorIfPresent);
}
}

View file

@ -0,0 +1,46 @@
package ru.spcex.clearing.backendapi.meta;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.PathResource;
import org.springframework.core.io.Resource;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Configuration
public class GetResponseFactoryTestConfiguration {
@Bean("metaJsonTestPath")
public Resource metaJsonPath() {
Path path = Paths.get("src", "main", "resources", "meta.json");
return new PathResource(path);
}
// @Value("file:${spring.config.location}/meta.json")
// private Resource meta;
//
@Bean("metaJsonTestRaw")
public String metaJsonRawString(@Qualifier("metaJsonTestPath") Resource meta) throws IOException {
if (!meta.isReadable()) throw new IllegalStateException("cannot read meta.json from meta " + meta.getFile());
return Files.readString(meta.getFile().toPath());
}
//
@Autowired
@Bean("metaJsonTest")
public MetaServer metaServer(@Qualifier("metaJsonTestRaw") String metaJson) throws JsonProcessingException {
ObjectMapper jsonObjectMapper = new ObjectMapper();
jsonObjectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
jsonObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MetaServer metaServer = jsonObjectMapper.readValue(metaJson, MetaServer.class);
metaServer.initAndValidate();
return metaServer;
}
}