diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/RegistryBuilder.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/RegistryBuilder.java new file mode 100644 index 000000000..45a21e5c2 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/RegistryBuilder.java @@ -0,0 +1,153 @@ +package ru.spcex.clearing.session.stage.impl; + +import ru.clearing.classes.statics.data.account.Account; +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.execution.ExecutionCommon; +import ru.clearing.classes.statics.data.execution.ExecutionDeposit; +import ru.clearing.classes.statics.data.execution.ExecutionFond; +import ru.clearing.classes.statics.data.misc.Market; +import ru.clearing.classes.statics.data.misc.Session; +import ru.clearing.classes.statics.data.registry.Registry; +import ru.clearing.classes.statics.data.registry.TradingClearingRegistry; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.session.stage.util.RegistryUtil; +import ru.spcex.platform.classes.base.interfaces.ExecutionType; +import ru.spcex.platform.enumeration.*; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public class RegistryBuilder { + public static RegistryBuilder builder() { + return new RegistryBuilder(); + } + + private RegistryBuilder() { + } + + private Imdg companyImdg; + private Imdg tradingClearingRegistryImdg; + private Imdg accountImdg; + private Imdg marketImdg; + private Imdg sessionImdg; + private ExecutionCommon exec; + private RegistryDesignation regDsgn; + + public RegistryBuilder imdg(ImdgProvider imdgProvider) { + this.companyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); + this.tradingClearingRegistryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_TradingClearingRegistry, TradingClearingRegistry.class); + this.accountImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Account, Account.class); + this.marketImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Market, Market.class); + this.sessionImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Session, Session.class); + return this; + } + + public RegistryBuilder exec(ExecutionCommon exec) { + this.exec = exec; + return this; + } + + public RegistryBuilder registryDesignation(RegistryDesignation registryDesignation) { + this.regDsgn = registryDesignation; + return this; + } + + + + public Registry build() { + ISide side = getSide(exec); + Registry reg = new Registry(); + reg.setCompanyId(exec.getCompanyId()); + Company company = searchCompany(); + reg.setTradingCode(company.getTradingCode()); + reg.setClearingCode(company.getClearingCode()); + reg.setShortName(company.getShortName()); + reg.setFullName(company.getFullName()); + TradingClearingRegistry tcr = searchTradingClearingRegistry(); + if ((regDsgn.equals(RegistryDesignation.O) && side.isBuy()) || (regDsgn.equals(RegistryDesignation.T) && side.isSell())) { + reg.setAccountId(tcr.getMoneyAccountId()); + reg.setRegistryInstrumentType(RegistryInstrumentType.M.getKey()); + reg.setBalanceDimension(BalanceDimension.MONY.getKey()); //fixme add second leg code branch + } else if ((regDsgn.equals(RegistryDesignation.T) && side.isBuy()) || (regDsgn.equals(RegistryDesignation.O) && side.isSell())) { + reg.setAccountId(tcr.getDepoAccountId()); + reg.setRegistryInstrumentType(RegistryInstrumentType.S.getKey()); + reg.setBalanceDimension(BalanceDimension.PICS.getKey()); //fixme add second leg code branch + } + Account account = accountImdg.getSingleObjectByID(reg.getAccountId()); + reg.setAccountType(account.getAccountType()); + reg.setAccount(account.getAccount()); + reg.setRegistryDesignation(regDsgn.getKey()); + reg.setRegistryCapacity(account.getAccountType()); + reg.setRegistryUnit(RegistryUnit.T.getKey()); + reg.setRegistryCode(RegistryUtil.clearingCode(reg)); + reg.setTradingClearingRegistryId(exec.getTradingClearingRegistryId()); + reg.setTradingClearingRegistry(tcr.getCode()); + reg.setRegistryStatus(RegistryStatus.PROC.getKey()); + reg.setSecurityId(exec.getSecurityId()); + reg.setSecuritySymbol(exec.getSecuritySymbol()); + switch (exec.type()) { + case ExecutionDeposit -> { + ExecutionDeposit execDep = (ExecutionDeposit) this.exec; + reg.setBalance((execDep).getFirstLegAmount()); + if (regDsgn.equals(RegistryDesignation.T)) { + reg.setSettledCredit((execDep).getFirstLegAmount()); + } else if (regDsgn.equals(RegistryDesignation.O)) { + reg.setSettledDebit((execDep).getFirstLegAmount()); + } + reg.setSettlementDate(execDep.getFirstLegSettlementDate()); //fixme add second leg code branch + reg.setSettlementCode(execDep.getFirstLegSettlementCode()); //fixme add second leg code branch + reg.setRefundDate(execDep.getSecondLegSettlementDate()); //fixme add second leg code branch?? + reg.setValueDate(execDep.getFirstLegSettlementDate()); + reg.setContract(execDep.getContract()); + } + case ExecutionFond -> { + ExecutionFond execFond = (ExecutionFond) this.exec; + reg.setBalance(execFond.getSettlementAmount()); + reg.setSettlementDate(execFond.getSettlementDate()); + reg.setSettlementCode(execFond.getSettlementCode()); + } + } + reg.setTradingDate(exec.getTradingDate()); + reg.setClearingDate(LocalDate.now()); + reg.setPrice(exec.getPrice()); + reg.setCounterPartyId(exec.getCounterPartyId()); + reg.setGroupId(groupId()); + reg.setSessionId(exec.getSessionId()); + reg.setSessionType(sessionType()); + return reg; + } + + private static DateTimeFormatter yyyyMMdd = DateTimeFormatter.ofPattern("yyyyMMdd"); + + private Long groupId() { + LocalDate now = LocalDate.now(); + Market market = marketImdg.getSingleObjectBySQL("code = '" + exec.getMarket() + "'"); + return Long.valueOf(now.format(yyyyMMdd) + exec.getExchangeExecutionId() + market.getId()); + } + + //можно передать из стейджа + private String sessionType() { + Session session = sessionImdg.getSingleObjectByID(exec.getSessionId()); + return session.getSessionType(); + } + + private Company searchCompany() { + return companyImdg.getSingleObjectBySQL("id = " + exec.getCompanyId()); + } + + private TradingClearingRegistry searchTradingClearingRegistry() { + return tradingClearingRegistryImdg.getSingleObjectBySQL("tradingClearingRegistryId = " + exec.getTradingClearingRegistryId()); + } + + private static ISide getSide(ExecutionCommon exec) { + if (exec.type().equals(ExecutionType.ExecutionDeposit)) { + return ISide.parse(MoneyFlowSide.class, exec.getSide()); + } else if (exec.type().equals(ExecutionType.ExecutionFond)) { + return ISide.parse(Side.class, exec.getSide()); + } else { + throw new RuntimeException("Unknown Execution type: " + exec.type()); + } + } +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/RegistryUpdater.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/RegistryUpdater.java new file mode 100644 index 000000000..98bc78a6d --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/RegistryUpdater.java @@ -0,0 +1,66 @@ +package ru.spcex.clearing.session.stage.impl; + +import ru.clearing.classes.statics.data.company.Company; +import ru.clearing.classes.statics.data.execution.ExecutionCommon; +import ru.clearing.classes.statics.data.registry.Registry; +import ru.clearing.classes.statics.data.registry.TradingClearingRegistry; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; + +public class RegistryUpdater { + public static RegistryUpdater updater() { + return new RegistryUpdater(); + } + + private RegistryUpdater() { + } + + private Imdg registryImdg; + private Imdg companyImdg; + private Imdg tradingClearingRegistryImdg; + private ExecutionCommon exec; + private Registry rgs; + + public RegistryUpdater imdg(ImdgProvider imdgProvider) { + this.registryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class); + this.companyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Company, Company.class); + this.tradingClearingRegistryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_TradingClearingRegistry, TradingClearingRegistry.class); + return this; + } + + public RegistryUpdater registry(Registry rgs) { + this.rgs = rgs; + return this; + } + + public RegistryUpdater exec(ExecutionCommon exec) { + this.exec = exec; + return this; + } + + public Registry update() { + Registry reg = new Registry(); + reg.setCompanyId(exec.getCompanyId()); + Company company = searchCompany(); + reg.setTradingCode(company.getTradingCode()); + reg.setClearingCode(company.getClearingCode()); + reg.setShortName(company.getShortName()); + reg.setFullName(company.getFullName()); + TradingClearingRegistry tcr = searchTradingClearingRegistry(); + return null; + } + + private Company searchCompany() { + return companyImdg.getSingleObjectBySQL("id = " + exec.getCompanyId()); + } + + private TradingClearingRegistry searchTradingClearingRegistry() { + return tradingClearingRegistryImdg.getSingleObjectBySQL("tradingClearingRegistryId = " + exec.getTradingClearingRegistryId()); + } + + + + +// public +} diff --git a/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/RequirmentsAndObligationCreation.java b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/RequirmentsAndObligationCreation.java new file mode 100644 index 000000000..894b0f197 --- /dev/null +++ b/clearing-parent/clearing-service/src/main/java/ru/spcex/clearing/session/stage/impl/RequirmentsAndObligationCreation.java @@ -0,0 +1,180 @@ +package ru.spcex.clearing.session.stage.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ru.clearing.classes.statics.data.execution.ExecutionCommon; +import ru.clearing.classes.statics.data.execution.ExecutionDeposit; +import ru.clearing.classes.statics.data.execution.ExecutionFond; +import ru.clearing.classes.statics.data.registry.Registry; +import ru.spcex.clearing.imdg.IMDGDistributedNames; +import ru.spcex.clearing.models.RegistryTradingParams; +import ru.spcex.clearing.service.builder.sql.RegistryCodeSqlBuilder; +import ru.spcex.clearing.session.stage.ISessionStage; +import ru.spcex.clearing.session.stage.StageResult; +import ru.spcex.clearing.session.stage.Task; +import ru.spcex.clearing.session.stage.TaskType; +import ru.spcex.platform.classes.base.interfaces.ExecutionType; +import ru.spcex.platform.enumeration.*; +import ru.spcex.platform.imdg.api.Imdg; +import ru.spcex.platform.imdg.api.ImdgProvider; +import ru.spcex.platform.imdg.api.predicate.ImdgPredicate; +import ru.spcex.platform.imdg.api.predicate.ImdgPredicateBuilder; +import ru.spcex.platform.utils.collection.Pair; + +import java.time.LocalDate; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiConsumer; + +@Service +public class RequirmentsAndObligationCreation implements ISessionStage { + private final Logger log = LoggerFactory.getLogger(getClass()); + private final Imdg registryImdg; + private final ImdgProvider imdgProvider; + + + @Autowired + public RequirmentsAndObligationCreation(ImdgProvider imdgProvider) { + this.registryImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Registry, Registry.class); + this.imdgProvider = imdgProvider; + } + + @SuppressWarnings("unchecked") + @Override + public StageResult submit(Task task) { + if (task.getTaskType() == TaskType.RequirementsAndObligationsCreate) { + return createRegisters((List) task.getData()); + } + throw new IllegalStateException("Unknown task type: " + task.getTaskType()); + } + + private StageResult createRegisters(List data) { + if (data.size() % 2 != 0) { + throw new IllegalStateException("Data size must be even (executions must match)"); + } + //см. описание к #matchExecutions + for (int i = 0; i < data.size(); ) { + Pair matched = matchExecutions(data, i); + if (matched == null) { + log.error("couldn't find match execution for id = {}", data.get(i).getId()); + i += 1; + continue; + } + i += 2; + ExecutionCommon partyExec = matched.getFirst(); + ExecutionCommon counterExec = matched.getSecond(); + + BiConsumer findAndUpdateOrCreate = (exec, regDsgn) -> + findReg(exec, regDsgn).ifPresentOrElse(registry -> { + RegistryUpdater.updater() + .imdg(imdgProvider) + .exec(partyExec) + .registry(registry) + .update(); + registryImdg.update(registry); + }, () -> { + Registry newRegister = RegistryBuilder.builder() + .imdg(imdgProvider) + .exec(partyExec) + .registryDesignation(regDsgn) + .build(); + registryImdg.insert(newRegister); + }); + findAndUpdateOrCreate.accept(partyExec, RegistryDesignation.O); + findAndUpdateOrCreate.accept(partyExec, RegistryDesignation.T); + findAndUpdateOrCreate.accept(counterExec, RegistryDesignation.O); + findAndUpdateOrCreate.accept(counterExec, RegistryDesignation.T); + } + return new StageResult<>(null, true); + } + + + private Optional findReg(ExecutionCommon exec, RegistryDesignation des) { + ISide side = getSide(exec); + RegistryTradingParams p; + if (side.isBuy() && des.equals(RegistryDesignation.O)) { + p = new RegistryTradingParams( + RegistryDesignation.O, RegistryInstrumentType.M, null, RegistryUnit.T + ); + } else if (side.isBuy() && des.equals(RegistryDesignation.T)) { + p = new RegistryTradingParams( + RegistryDesignation.T, RegistryInstrumentType.S, null, RegistryUnit.T + ); + } else if (side.isSell() && des.equals(RegistryDesignation.O)) { + p = new RegistryTradingParams( + RegistryDesignation.O, RegistryInstrumentType.S, null, RegistryUnit.T + ); + } else if (side.isSell() && des.equals(RegistryDesignation.T)) { + p = new RegistryTradingParams( + RegistryDesignation.T, RegistryInstrumentType.M, null, RegistryUnit.T + ); + } else { + throw new IllegalStateException("cannot construct for " + des + " " + side); + } + String sql = RegistryCodeSqlBuilder.getInstance(p).build(); + ImdgPredicateBuilder pb = registryImdg.predicateBuilder(); + ImdgPredicate rgstrPredicate = pb.and(pb.sql(sql), + pb.equals("tradingClearingRegistryId", exec.getTradingClearingRegistryId()), + pb.equals("companyId", exec.getCompanyId()), + pb.equals("settlementDate", settlementDt(exec)) + ); + return Optional.ofNullable(registryImdg.getSingleObjectByPredicate(rgstrPredicate)); + } + + private LocalDate settlementDt(ExecutionCommon exec) { + if (exec.type().equals(ExecutionType.ExecutionDeposit)) { + return ((ExecutionDeposit) exec).getFirstLegSettlementDate(); + } else if (exec.type().equals(ExecutionType.ExecutionFond)) { + return ((ExecutionFond) exec).getSettlementDate(); + } else { + throw new RuntimeException("Unknown Execution type: " + exec.type()); + } + } + + + /** + * входные данные отсортированы по exchangeExecutionId. + * сортировка выполнена на предыдущем шаге. (такое описание шагов в ТЗ) + * по идее, Execution с одинаковым exchangeExecutionId должно быть всего два. + * если это не так, передалать на коллекцию Long executionId в правильном порядке + * и для каждого искать мэтч отдельно в Imdg, с сохранением уже обработанных для избежания дублирования + */ + private Pair matchExecutions(List data, int i) { + //if the last execution, then no match + if (i >= data.size() - 1) { + return null; + } + ExecutionCommon exec1 = data.get(i); + ExecutionCommon exec2 = data.get(i + 1); + //Встречная сделка контрагента выбирается из executionDeposit/Fond по условию: + //exchangeExecutionId=currentExecutionDeposit/Fond.exchangeExecutionId + //и [side=SELL (если currentExecutionDeposit/Fond.side=BUY) или side=BUY (если currentExecutionDeposit/Fond.side=SELL) по справочнику moneyFlowSide или справочнику side в зависимости от секции обрабатываемой сделки] + //и companyId=currentExecutionDeposit/Fond.counterPartyId + boolean valid = true; + if (!Objects.equals(exec1.getExchangeExecutionId(), exec2.getExchangeExecutionId())) { + valid = false; + } else if (Objects.equals(getSide(exec1), getSide(exec2))) { + valid = false; + } else if (!Objects.equals(exec1.getCounterPartyId(), exec2.getCounterPartyId())) { + valid = false; + } + if (!valid) { + log.error("FATAL skipping ExchangeExecutionId: {}", exec1.getId()); + } + return new Pair<>(exec1, exec2); + } + + private static ISide getSide(ExecutionCommon exec) { + if (exec.type().equals(ExecutionType.ExecutionDeposit)) { + return ISide.parse(MoneyFlowSide.class, exec.getSide()); + } else if (exec.type().equals(ExecutionType.ExecutionFond)) { + return ISide.parse(Side.class, exec.getSide()); + } else { + throw new RuntimeException("Unknown Execution type: " + exec.type()); + } + } + +} diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/BalanceDimension.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/BalanceDimension.java new file mode 100644 index 000000000..8251c48a4 --- /dev/null +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/BalanceDimension.java @@ -0,0 +1,20 @@ +package ru.spcex.platform.enumeration; + +import ru.spcex.platform.utils.enumeration.IEnumKey; + +public enum BalanceDimension implements IEnumKey { + MONY("MONY"), + PICS("PICS"), + ; + + private final String key; + + BalanceDimension(String key) { + this.key = key; + } + + @Override + public String getKey() { + return key; + } +} diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/ISide.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/ISide.java new file mode 100644 index 000000000..4a5256b8b --- /dev/null +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/ISide.java @@ -0,0 +1,15 @@ +package ru.spcex.platform.enumeration; + +import ru.spcex.platform.utils.enumeration.IEnumKey; + +public interface ISide { + boolean isBuy(); + boolean isSell(); + default boolean isOpposite(ISide other) { + return (isBuy() && other.isSell()) || (isSell() && other.isBuy()); + } + + static & IEnumKey & ISide> ISide parse(Class clazz, String key) { + return IEnumKey.getEnumByKey(clazz, key); + } +} diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/MoneyFlowSide.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/MoneyFlowSide.java index 1b1111295..b275279de 100644 --- a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/MoneyFlowSide.java +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/MoneyFlowSide.java @@ -2,7 +2,7 @@ package ru.spcex.platform.enumeration; import ru.spcex.platform.utils.enumeration.IEnumKey; -public enum MoneyFlowSide implements IEnumKey { +public enum MoneyFlowSide implements IEnumKey, ISide { BUY("BUY"), SELL("SELL"), ; @@ -17,4 +17,15 @@ public enum MoneyFlowSide implements IEnumKey { public String getKey() { return key; } + + + @Override + public boolean isBuy() { + return equals(BUY); + } + + @Override + public boolean isSell() { + return equals(SELL); + } } diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/RegistryStatus.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/RegistryStatus.java index dd948e3b9..451a06ef8 100644 --- a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/RegistryStatus.java +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/RegistryStatus.java @@ -3,7 +3,7 @@ package ru.spcex.platform.enumeration; import ru.spcex.platform.utils.enumeration.IEnumKey; public enum RegistryStatus implements IEnumKey { - OK("OK"), + OK("OK"), PROC("PROC"), ; private final String key; diff --git a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/Side.java b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/Side.java index d33888a93..2efb0196d 100644 --- a/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/Side.java +++ b/platform-parent/platform-enum/src/main/java/ru/spcex/platform/enumeration/Side.java @@ -2,7 +2,7 @@ package ru.spcex.platform.enumeration; import ru.spcex.platform.utils.enumeration.IEnumKey; -public enum Side implements IEnumKey { +public enum Side implements IEnumKey, ISide { BUY("B"), SELL("S"), ; @@ -17,4 +17,15 @@ public enum Side implements IEnumKey { public String getKey() { return key; } + + + @Override + public boolean isBuy() { + return equals(BUY); + } + + @Override + public boolean isSell() { + return equals(SELL); + } } diff --git a/platform-parent/platform-imdg-api-hazelcast-impl/src/main/java/ru/spcex/platform/imdg/iml/hazelcast/adapter/ImdgHazelcast.java b/platform-parent/platform-imdg-api-hazelcast-impl/src/main/java/ru/spcex/platform/imdg/iml/hazelcast/adapter/ImdgHazelcast.java index a544c3a45..4cbde8b72 100644 --- a/platform-parent/platform-imdg-api-hazelcast-impl/src/main/java/ru/spcex/platform/imdg/iml/hazelcast/adapter/ImdgHazelcast.java +++ b/platform-parent/platform-imdg-api-hazelcast-impl/src/main/java/ru/spcex/platform/imdg/iml/hazelcast/adapter/ImdgHazelcast.java @@ -251,6 +251,15 @@ public class ImdgHazelcast implements Imdg { return searchResult; } + @Override + public T getSingleObjectByPredicate(ImdgPredicate predicate) { + Collection els = getCollectionObjectsByPredicate(predicate); + if (els.size() > 1) { + throw new RuntimeException("More than one element found by predicate - " + els.size()); + } + return els.iterator().next(); + } + @Override public ImdgPredicateBuilder predicateBuilder() { return ImdgPredicateBuilderHazelcast.instance(); diff --git a/platform-parent/platform-imdg-api/src/main/java/ru/spcex/platform/imdg/api/Imdg.java b/platform-parent/platform-imdg-api/src/main/java/ru/spcex/platform/imdg/api/Imdg.java index 68007e757..091dc44fa 100644 --- a/platform-parent/platform-imdg-api/src/main/java/ru/spcex/platform/imdg/api/Imdg.java +++ b/platform-parent/platform-imdg-api/src/main/java/ru/spcex/platform/imdg/api/Imdg.java @@ -101,6 +101,11 @@ public interface Imdg { throw new UnsupportedOperationException("not implemented getCollectionObjectsByPredicate"); } + // Predicate API + default T getSingleObjectByPredicate(ImdgPredicate predicate) { + throw new UnsupportedOperationException("not implemented getSingleObjectByPredicate"); + } + default ImdgPredicateBuilder predicateBuilder() { throw new UnsupportedOperationException("not implemented predicateBuilder API"); }