diff --git a/clearing-classes/pom.xml b/clearing-classes/pom.xml new file mode 100644 index 000000000..402e26058 --- /dev/null +++ b/clearing-classes/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + clearing-classes + Clearing classes + Clearing classes module + jar + + + clearing + ru.spcex.clearing + SPCEX-1.0.0.0 + + + + 8 + 8 + + + + + + + + + + + + + + + ${project.artifactId}-${project.version} + + \ No newline at end of file diff --git a/clearing-classes/src/main/java/ru/clearing/classes/ConstSerializable.java b/clearing-classes/src/main/java/ru/clearing/classes/ConstSerializable.java new file mode 100644 index 000000000..8e607f379 --- /dev/null +++ b/clearing-classes/src/main/java/ru/clearing/classes/ConstSerializable.java @@ -0,0 +1,8 @@ +package ru.clearing.classes; + +/** + * В случае любых изменений модуля classes необходимо изменить serialVersionUID++ + */ +public interface ConstSerializable { + long serialVersionUID = 293236453420L; +} diff --git a/clearing-classes/src/main/java/ru/clearing/classes/StaticData/Company/Company.java b/clearing-classes/src/main/java/ru/clearing/classes/StaticData/Company/Company.java new file mode 100644 index 000000000..baaadd980 --- /dev/null +++ b/clearing-classes/src/main/java/ru/clearing/classes/StaticData/Company/Company.java @@ -0,0 +1,59 @@ +package ru.clearing.classes.StaticData.Company; + +import ru.clearing.classes.ConstSerializable; +import ru.clearing.classes.objects.BusinessObject; + +/** + * + */ +public class Company extends BusinessObject { + private static final long serialVersionUID = ConstSerializable.serialVersionUID; + + private String clearingCode; + private String exchangeCode; + private String fullName; +// private CompanyInfo profile; // todo impl profile + private Long statusId; // WorkflowStatusDictionary + + @Override + public String toString() { + return "Company{" + + "clearingCode='" + clearingCode + '\'' + + ", exchangeCode='" + exchangeCode + '\'' + + ", fullName='" + fullName + '\'' + + ", statusId=" + statusId + + '}'; + } + + public String getClearingCode() { + return clearingCode; + } + + public void setClearingCode(String clearingCode) { + this.clearingCode = clearingCode; + } + + public String getExchangeCode() { + return exchangeCode; + } + + public void setExchangeCode(String exchangeCode) { + this.exchangeCode = exchangeCode; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public Long getStatusId() { + return statusId; + } + + public void setStatusId(Long statusId) { + this.statusId = statusId; + } +} \ No newline at end of file diff --git a/clearing-classes/src/main/java/ru/clearing/classes/StaticData/Company/CompanyRoleSet.java b/clearing-classes/src/main/java/ru/clearing/classes/StaticData/Company/CompanyRoleSet.java new file mode 100644 index 000000000..7304ab48b --- /dev/null +++ b/clearing-classes/src/main/java/ru/clearing/classes/StaticData/Company/CompanyRoleSet.java @@ -0,0 +1,30 @@ +package ru.clearing.classes.StaticData.Company; + +import ru.clearing.classes.ConstSerializable; +import ru.clearing.classes.objects.ObjectBase; + +/** + * DB: COMPANY_ROLE_SET + */ +public class CompanyRoleSet extends ObjectBase { + private static final long serialVersionUID = ConstSerializable.serialVersionUID; + + private Long roleId; + private Long companyId; + + public Long getRoleId() { + return roleId; + } + + public void setRoleId(Long roleId) { + this.roleId = roleId; + } + + public Long getCompanyId() { + return companyId; + } + + public void setCompanyId(Long companyId) { + this.companyId = companyId; + } +} \ No newline at end of file diff --git a/clearing-classes/src/main/java/ru/clearing/classes/StaticData/User/ClearingUser.java b/clearing-classes/src/main/java/ru/clearing/classes/StaticData/User/ClearingUser.java new file mode 100644 index 000000000..a0e4f93d6 --- /dev/null +++ b/clearing-classes/src/main/java/ru/clearing/classes/StaticData/User/ClearingUser.java @@ -0,0 +1,27 @@ +package ru.clearing.classes.StaticData.User; + + +import ru.clearing.classes.ConstSerializable; +import ru.clearing.classes.objects.BusinessObject; + +/** + * Database table: USER_CLEARING todo name for user ? + */ +public class ClearingUser extends BusinessObject { + private static final long serialVersionUID = ConstSerializable.serialVersionUID; + +// private String idpUuid; +// +// public ClearingUser() { +// super(); +// } +// +// public String toString() { +// return String.format("%s{id=%d, idpUuid='%s'}", getClass().getSimpleName(), getId(), getIdpUuid()); +// } +// +// public String getIdpUuid() { +// return idpUuid; +// } + +} \ No newline at end of file diff --git a/clearing-classes/src/main/java/ru/clearing/classes/objects/BusinessEvent.java b/clearing-classes/src/main/java/ru/clearing/classes/objects/BusinessEvent.java new file mode 100644 index 000000000..d851b81ba --- /dev/null +++ b/clearing-classes/src/main/java/ru/clearing/classes/objects/BusinessEvent.java @@ -0,0 +1,25 @@ +package ru.clearing.classes.objects; + +import ru.clearing.classes.ConstSerializable; + +import java.util.Date; + +/** + * + */ +public abstract class BusinessEvent extends ObjectBase { + static final long serialVersionUID = ConstSerializable.serialVersionUID; + + private Date eventTime; + + public BusinessEvent() { + } + + public Date getEventTime() { + return eventTime; + } + + public void setEventTime(Date eventTime) { + this.eventTime = eventTime; + } +} \ No newline at end of file diff --git a/clearing-classes/src/main/java/ru/clearing/classes/objects/BusinessObject.java b/clearing-classes/src/main/java/ru/clearing/classes/objects/BusinessObject.java new file mode 100644 index 000000000..8c67d25a8 --- /dev/null +++ b/clearing-classes/src/main/java/ru/clearing/classes/objects/BusinessObject.java @@ -0,0 +1,31 @@ +package ru.clearing.classes.objects; + +import ru.clearing.classes.ConstSerializable; + +import java.time.Instant; + +/** + * UUID - Long based with field object + */ +public abstract class BusinessObject extends ObjectBase { + static final long serialVersionUID = ConstSerializable.serialVersionUID; + + private Instant created; + private Instant updated; + + public Instant getCreated() { + return created; + } + + public void setCreated(Instant created) { + this.created = created; + } + + public Instant getUpdated() { + return updated; + } + + public void setUpdated(Instant updated) { + this.updated = updated; + } +} \ No newline at end of file diff --git a/clearing-classes/src/main/java/ru/clearing/classes/objects/ObjectBase.java b/clearing-classes/src/main/java/ru/clearing/classes/objects/ObjectBase.java new file mode 100644 index 000000000..80e45d86e --- /dev/null +++ b/clearing-classes/src/main/java/ru/clearing/classes/objects/ObjectBase.java @@ -0,0 +1,55 @@ +package ru.clearing.classes.objects; + +import ru.clearing.classes.ConstSerializable; +import ru.clearing.common.interfaces.WithId; + +import java.io.Serializable; + +/** + * Long ID - based primary key object + */ +public abstract class ObjectBase implements WithId, Serializable { + static final long serialVersionUID = ConstSerializable.serialVersionUID; + + protected Long id; + public ObjectBase(){ + } + + public ObjectBase(Long id) { + this.id = id; + } + + public static Long getIdOrNull(WithId o) { + return o == null ? null : o.getId(); + } + + @Override + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public String toString() { + return String.format("%s{id=%d}", getClass().getSimpleName(), getId()); + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ObjectBase otcObject = (ObjectBase) o; + + return id.equals(otcObject.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } +} \ No newline at end of file diff --git a/clearing-classes/src/main/java/ru/clearing/common/interfaces/WithId.java b/clearing-classes/src/main/java/ru/clearing/common/interfaces/WithId.java new file mode 100644 index 000000000..2db02001d --- /dev/null +++ b/clearing-classes/src/main/java/ru/clearing/common/interfaces/WithId.java @@ -0,0 +1,5 @@ +package ru.clearing.common.interfaces; + +public interface WithId { + Long getId(); +} diff --git a/clearing-classes/target/dfa-classes-DFA-1.0.0.0.jar b/clearing-classes/target/dfa-classes-DFA-1.0.0.0.jar new file mode 100644 index 000000000..1f3bc1eea Binary files /dev/null and b/clearing-classes/target/dfa-classes-DFA-1.0.0.0.jar differ diff --git a/clearing-classes/target/maven-archiver/pom.properties b/clearing-classes/target/maven-archiver/pom.properties new file mode 100644 index 000000000..57b18811d --- /dev/null +++ b/clearing-classes/target/maven-archiver/pom.properties @@ -0,0 +1,4 @@ +#Created by Apache Maven 3.6.3 +groupId=ru.spcex.clearing +artifactId=clearing-classes +version=SPCEX-1.0.0.0 diff --git a/clearing-classes/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/clearing-classes/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 000000000..f78dea770 --- /dev/null +++ b/clearing-classes/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,8 @@ +ru\clearing\classes\objects\ObjectBase.class +ru\clearing\classes\ConstSerializable.class +ru\clearing\classes\StaticData\Company\CompanyRoleSet.class +ru\clearing\classes\objects\BusinessEvent.class +ru\clearing\classes\StaticData\Company\Company.class +ru\clearing\classes\StaticData\User\ClearingUser.class +ru\clearing\common\interfaces\WithId.class +ru\clearing\classes\objects\BusinessObject.class diff --git a/clearing-classes/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/clearing-classes/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 000000000..50e2706e2 --- /dev/null +++ b/clearing-classes/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,8 @@ +E:\MFD_GIT_REPO\clearing\clearing-classes\src\main\java\ru\clearing\classes\StaticData\User\ClearingUser.java +E:\MFD_GIT_REPO\clearing\clearing-classes\src\main\java\ru\clearing\classes\objects\ObjectBase.java +E:\MFD_GIT_REPO\clearing\clearing-classes\src\main\java\ru\clearing\classes\ConstSerializable.java +E:\MFD_GIT_REPO\clearing\clearing-classes\src\main\java\ru\clearing\classes\StaticData\Company\Company.java +E:\MFD_GIT_REPO\clearing\clearing-classes\src\main\java\ru\clearing\classes\objects\BusinessEvent.java +E:\MFD_GIT_REPO\clearing\clearing-classes\src\main\java\ru\clearing\classes\objects\BusinessObject.java +E:\MFD_GIT_REPO\clearing\clearing-classes\src\main\java\ru\clearing\classes\StaticData\Company\CompanyRoleSet.java +E:\MFD_GIT_REPO\clearing\clearing-classes\src\main\java\ru\clearing\common\interfaces\WithId.java diff --git a/clearing-dictionary/.gitignore b/clearing-dictionary/.gitignore new file mode 100644 index 000000000..9ea567857 --- /dev/null +++ b/clearing-dictionary/.gitignore @@ -0,0 +1,12 @@ +*.iml +target +lib +.idea +*.log +temp +otc.logs_IS_UNDEFINED +.gitconfig +dependency-reduced-pom.xml +*/otc-test/src/test/resources/dataForDb/*.sql +**/out +preview \ No newline at end of file diff --git a/clearing-dictionary/CHANGELOG.md b/clearing-dictionary/CHANGELOG.md new file mode 100644 index 000000000..5453895a1 --- /dev/null +++ b/clearing-dictionary/CHANGELOG.md @@ -0,0 +1,3 @@ +1.0.0 +------------------------------ +- \ No newline at end of file diff --git a/clearing-dictionary/pom.xml b/clearing-dictionary/pom.xml new file mode 100644 index 000000000..dd81e0b7e --- /dev/null +++ b/clearing-dictionary/pom.xml @@ -0,0 +1,118 @@ + + + 4.0.0 + + clearing-dictionary + PLATFORM dictionary + PLATFORM dictionary + jar + SPCEX-1.0.0.0 + + + clearing + ru.spcex.clearing + SPCEX-1.0.0.0 + + + + 5.4.2 + + + + + + + org.junit.jupiter + junit-jupiter + ${external_libraries.junit-jupiter.version} + test + + + junit + junit + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + org.apache.commons + commons-lang3 + 3.7 + + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + ${project.java.version} + ${project.java.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.0 + + + + true + true + + + ${built.by} + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + true + true + + + ${built.by} + + + + + + + + + + + + + \ No newline at end of file diff --git a/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/AbstractDictionary.java b/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/AbstractDictionary.java new file mode 100644 index 000000000..f9ebdabe2 --- /dev/null +++ b/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/AbstractDictionary.java @@ -0,0 +1,29 @@ +package ru.clearing.platform.dictionary; + +public abstract class AbstractDictionary implements Dictionary { + static final long serialVersionUID = ConstDictionarySerializable.serialVersionUID; + + protected Long id; + protected String name; + + @Override + public Long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + +} \ No newline at end of file diff --git a/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/ConstDictionarySerializable.java b/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/ConstDictionarySerializable.java new file mode 100644 index 000000000..7f7028c67 --- /dev/null +++ b/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/ConstDictionarySerializable.java @@ -0,0 +1,5 @@ +package ru.clearing.platform.dictionary; + +public interface ConstDictionarySerializable { + long serialVersionUID = 7020097431335696640L; +} diff --git a/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/Dictionary.java b/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/Dictionary.java new file mode 100644 index 000000000..a24bd2477 --- /dev/null +++ b/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/Dictionary.java @@ -0,0 +1,17 @@ +package ru.clearing.platform.dictionary; + +import java.io.Serializable; + +/** + * Словарь. + */ +public interface Dictionary extends Serializable { + + Long getId(); + + void setId(Long id); + + String getName(); + + void setName(String id); +} diff --git a/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/WorkflowStatusDictionary.java b/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/WorkflowStatusDictionary.java new file mode 100644 index 000000000..5864f07ee --- /dev/null +++ b/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/WorkflowStatusDictionary.java @@ -0,0 +1,9 @@ +package ru.clearing.platform.dictionary; + +/** + * Database table: WORKFLOW_STATUS_DICTIONARY + */ +public class WorkflowStatusDictionary extends AbstractDictionary { + private static final long serialVersionUID = ConstDictionarySerializable.serialVersionUID; + +} \ No newline at end of file diff --git a/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/special/CountryCode.java b/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/special/CountryCode.java new file mode 100644 index 000000000..5156a9120 --- /dev/null +++ b/clearing-dictionary/src/main/java/ru/clearing/platform/dictionary/special/CountryCode.java @@ -0,0 +1,47 @@ +package ru.clearing.platform.dictionary.special; + +import ru.clearing.platform.dictionary.ConstDictionarySerializable; +import ru.clearing.platform.dictionary.Dictionary; + +import java.io.Serializable; + +/** + * DB table: COUNTRY_CODE_DICTIONARY + * Dictionary + */ +public class CountryCode implements Serializable, Dictionary { + static final long serialVersionUID = ConstDictionarySerializable.serialVersionUID; + + private Long id; + private String name; + private String countryCode; + + @Override + public String toString() { + return String.format("CountryCode{id=%d, countryCode='%s'}", id, countryCode); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCountryCode() { + return countryCode; + } + + public void setCountryCode(String countryCode) { + this.countryCode = countryCode; + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 72f449272..f8f24506b 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,8 @@ frontendapi storage db-scripts + clearing-classes + clearing-dictionary @@ -27,6 +29,9 @@ SPCEX-1.0.0.0 2.3.12.RELEASE + + + 3.12.4 diff --git a/storage/pom.xml b/storage/pom.xml index 7066ccccb..20925b6be 100644 --- a/storage/pom.xml +++ b/storage/pom.xml @@ -17,10 +17,6 @@ - - org.springframework - spring-jdbc - org.springframework.boot spring-boot-starter @@ -29,6 +25,38 @@ org.springframework.boot spring-boot-autoconfigure + + ru.spcex.clearing + clearing-classes + SPCEX-1.0.0.0 + compile + + + ru.spcex.clearing + clearing-dictionary + SPCEX-1.0.0.0 + compile + + + + + + org.springframework + spring-jdbc + + + com.mchange + c3p0 + 0.9.5.2 + + + + + com.hazelcast + hazelcast-all + ${external_libraries.hazelcast.version} + + jar/${project.artifactId} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/base/BusinessEventMapStore.java b/storage/src/main/java/ru/spcex/clearing/storage/base/BusinessEventMapStore.java new file mode 100644 index 000000000..84b7d83c7 --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/base/BusinessEventMapStore.java @@ -0,0 +1,61 @@ +package ru.spcex.clearing.storage.base; + +import org.springframework.jdbc.core.JdbcTemplate; +import ru.clearing.classes.objects.BusinessEvent; +import ru.clearing.classes.objects.BusinessObject; +import ru.spcex.clearing.storage.utils.TimeUtil; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public abstract class BusinessEventMapStore extends ObjectBaseMapStore { + + public BusinessEventMapStore(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + public final Iterable loadAllKeys() { + return Collections.emptyList(); + } + + @Override + public final T load(Long id) { + return null; + } + + @Override + public final Collection load(Collection keys) { + return Collections.emptyList(); + } + + @Override + public final Map loadAll(Collection keys) { + return Collections.emptyMap(); + } + + /** + * Заполняет поля:
+ * uuid
+ * eventTime
+ * ownerId
+ * eventTypeId + * + * @param businessEvent объект для заполнения + * @param rs resultSet для выгрузки + */ + public void fillBusinessEventFields(BusinessEvent businessEvent, ResultSet rs) throws SQLException { + businessEvent.setEventTime(TimeUtil.toUtilDate(rs.getDate("eventtime"))); + businessEvent.setId(getLong(rs, "id")); + } + + public void fillBusinessObjectFieds(BusinessObject businessObject, ResultSet rs) throws SQLException { + businessObject.setId(rs.getObject("orderbondid", Long.class)); + businessObject.setUpdated(TimeUtil.fromDate(rs.getDate("updated"))); + businessObject.setCreated(TimeUtil.fromDate(rs.getDate("created"))); + } + +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/base/BusinessObjectMapStore.java b/storage/src/main/java/ru/spcex/clearing/storage/base/BusinessObjectMapStore.java new file mode 100644 index 000000000..64e3093cf --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/base/BusinessObjectMapStore.java @@ -0,0 +1,35 @@ +package ru.spcex.clearing.storage.base; + +import org.springframework.jdbc.core.JdbcTemplate; +import ru.clearing.classes.objects.BusinessObject; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Шаблон для загрузки BusinessObject-ов. + */ +public abstract class BusinessObjectMapStore extends ObjectBaseMapStore { + + public BusinessObjectMapStore(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + /** + * Заполняет стандартные поля для BusinessObject из ResultSet + * + * @param businessObject дозаполнить стандартные поля этому объекту + * @param rs из ResultSet + * @throws SQLException - error + */ + protected void fillBusinessObjectFields(BusinessObject businessObject, ResultSet rs) throws SQLException { + fillBusinessObjectFields(businessObject, rs, "id"); + } + + protected void fillBusinessObjectFields(BusinessObject businessObject, ResultSet rs, String idName) throws SQLException { + businessObject.setUpdated(getInstantFromTimestamp(rs, "updated")); + businessObject.setCreated(getInstantFromTimestamp(rs, "created")); + businessObject.setId(getLong(rs, idName)); + } + +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/base/DictionaryMapStore.java b/storage/src/main/java/ru/spcex/clearing/storage/base/DictionaryMapStore.java new file mode 100644 index 000000000..2903d40e2 --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/base/DictionaryMapStore.java @@ -0,0 +1,91 @@ +package ru.spcex.clearing.storage.base; + +import com.hazelcast.core.MapLoader; +import ru.clearing.platform.dictionary.Dictionary; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +import static ru.spcex.clearing.storage.base.ObjectBaseMapStore.MAX_IN_CLAUSE_SIZE; + +/** + * Store для словарей стандартных - из ID, NAME. + */ +public abstract class DictionaryMapStore implements MapLoader { + private static Logger log = LoggerFactory.getLogger(DictionaryMapStore.class); + + protected NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + protected final JdbcTemplate jdbcTemplate; + + protected DictionaryMapStore(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + } + + public abstract String getTableName(); + + public abstract T getDictionaryObject(); + + protected T mapRow(ResultSet rs, int rowNum) throws SQLException { + T dictionary = getDictionaryObject(); + dictionary.setId(rs.getLong("id")); + dictionary.setName(rs.getString("name")); + return dictionary; + } + + private Collection loadCollection(Collection keys) { + Map> paramMap = Collections.singletonMap("ids", keys); + return namedParameterJdbcTemplate.query("select * from " + getTableName() + " where id in (:ids)", paramMap, this::mapRow); + } + + @Override + public T load(Long id) { + Collection rows; + List list = Collections.singletonList(id); + try { + rows = loadCollection(list); + } catch (Throwable e) { // one retry + log.trace("Retry load after {}", e.toString()); + rows = loadCollection(list); + } + return DataAccessUtils.singleResult(rows); + } + + @Override + public Map loadAll(Collection keys) { + log.trace("loadAll from dictionary {} {} keys", getTableName(), keys.size()); + Map result = new HashMap<>(); + long start = System.currentTimeMillis(); + + // загрузить данные по ключам частями, чтобы не выйти за ограничения базы по кол-ву элементов в in clause + List keysSubList = new ArrayList<>(MAX_IN_CLAUSE_SIZE); + for (Iterator iterator = keys.iterator(); iterator.hasNext(); ) { + Long key = iterator.next(); + keysSubList.add(key); + if (keysSubList.size() == MAX_IN_CLAUSE_SIZE || !iterator.hasNext()) { + Collection rows = loadCollection(keysSubList); + for (T row : rows) { + result.put(row.getId(), row); + } + keysSubList.clear(); + } + } + + log.trace("loadAll from dictionary {} {} keys done in {} ms", getTableName(), keys.size(), (System.currentTimeMillis() - start)); + + return result; + } + + @Override + public Iterable loadAllKeys() { + log.debug("loadAllKeys from " + getTableName()); + return jdbcTemplate.query("select id from " + getTableName(), (rs, rowNum) -> rs.getLong("id")); + } +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/base/LoggingMapStore.java b/storage/src/main/java/ru/spcex/clearing/storage/base/LoggingMapStore.java new file mode 100644 index 000000000..622001a29 --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/base/LoggingMapStore.java @@ -0,0 +1,94 @@ +package ru.spcex.clearing.storage.base; + +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.MapLoaderLifecycleSupport; +import com.hazelcast.core.MapStore; +//import com.opencsv.CSVWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.NonNull; +import ru.clearing.classes.objects.ObjectBase; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Properties; +import java.util.function.Supplier; + +/** + * MapStore записывающий изменения в файл + * + * @param + */ +public abstract class LoggingMapStore implements MapStore, MapLoaderLifecycleSupport { + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + +// private CSVWriter csvWriter; todo подключить OpenCSV и настройку про логирование данных добавить + +// private Supplier recoveryPath = () -> DfaConfig.get().getRoot().getSettings().getRecoveryLogPath(); + + @Override + public void init(HazelcastInstance hazelcastInstance, Properties properties, String mapName) { +// try { +// String recoveryLogPath = recoveryPath.get(); +// if (recoveryLogPath != null) { +// SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); +// File recoveryDir = new File(recoveryLogPath + File.separator + simpleDateFormat.format(new Date())); +// recoveryDir.mkdirs(); +// File recoveryFile = new File(recoveryDir, getTableName() + ".csv"); +// csvWriter = new CSVWriter(new BufferedWriter(new FileWriter(recoveryFile)), ';'); +// log.info("recovery log started {}", recoveryFile); +// } +// } catch (Exception e) { +// log.error("", e); +// } + } + +// public void setRecoveryPath(Supplier recoveryPath) { +// this.recoveryPath = recoveryPath; +// } + + @Override + public void destroy() { +// try { +// if (csvWriter != null) +// csvWriter.close(); +// } catch (IOException e) { +// log.error("", e); +// } + } + + public abstract String getTableName(); + + protected void logHeaders(@NonNull String[] headers) { + try { +// if (csvWriter != null) { +// csvWriter.writeNext(headers); +// csvWriter.flush(); +// } + } catch (Exception e) { + log.error("", e); + } + } + + protected void logValues(@NonNull List valuesList) { + try { +// if (csvWriter != null) { +// for (Object[] values : valuesList) { +// String[] nextLine = new String[values.length]; +// for (int i = 0; i < values.length; i++) { +// nextLine[i] = String.valueOf(values[i]); +// } +// csvWriter.writeNext(nextLine); +// } +// csvWriter.flush(); +// } + } catch (Exception e) { + log.error("", e); + } + } +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/base/ObjectBaseMapStore.java b/storage/src/main/java/ru/spcex/clearing/storage/base/ObjectBaseMapStore.java new file mode 100644 index 000000000..fa60ad33a --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/base/ObjectBaseMapStore.java @@ -0,0 +1,174 @@ +package ru.spcex.clearing.storage.base; +import org.springframework.dao.support.DataAccessUtils; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import ru.clearing.classes.objects.ObjectBase; +import ru.spcex.clearing.storage.utils.DbUtilsHelper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.*; + +public abstract class ObjectBaseMapStore extends SimpleObjectMapStore { + /** + * См. legalEntityProfileToSQLArgs() + */ + protected static final String LEGAL_ENTITY_PROFILE_STATEMENT_ARGUMENT = + "description, domicilecountrycodeid, isprofessional, organizationtypeid, ogrn, name," + + " fullname, nameeng, fullnameeng, kpp, contactphone, contactemail, tsedallowedid, edomail," + + " nsdallowedid, otcmonitorallowedid, otcmonitorobligedid"; + + protected static final String insertChargeDefinitionStatement = DbUtilsHelper.fillValuesBlock( + "UPDATE OR INSERT INTO CHARGEDEFINITION (id, chargeamount, chargedirectionid, " + + " chargesettlementamount, chargetypeid, netincluded, currencycodeid, moexexchangecommission, userid, partyid)" + + " values %values_block% matching (id);" + ); + public static final int MAX_IN_CLAUSE_SIZE = 1000; + + protected NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + public ObjectBaseMapStore(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + } + +// @Override +// public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { +// this.jdbcTemplate = jdbcTemplate; +// this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); +// } + + @Override + public T load(Long id) { + if (!isLoadable(id)) return null; + Collection rows; + List list = Collections.singletonList(id); + try { + rows = load(list); + } catch (Throwable e) { // one retry + rows = load(list); + } + T obj = DataAccessUtils.singleResult(rows); + if (obj != null && isLoadable(obj)) + return obj; + else + return null; + } + + protected boolean isLoadable(Long id) { + return true; + } + + protected boolean isLoadable(T obj) { + return true; + } + + @Override + public Map loadAll(Collection keys) { + log.debug("loadAll from " + getTableName() + " " + keys.size() + " keys"); + Map result = new HashMap<>(); + long start = System.currentTimeMillis(); + + // загрузить данные по ключам частями, чтобы не выйти за ограничения базы по кол-ву элементов в in clause + List keysSubList = new ArrayList<>(MAX_IN_CLAUSE_SIZE); + for (Iterator iterator = keys.iterator(); iterator.hasNext(); ) { + Long key = iterator.next(); + keysSubList.add(key); + if (keysSubList.size() == MAX_IN_CLAUSE_SIZE || !iterator.hasNext()) { + Collection rows = load(keysSubList); + for (T row : rows) { + result.put(row.getId(), row); + } + keysSubList.clear(); + } + } + + log.debug("loadAll from " + getTableName() + " " + keys.size() + " keys done in " + (System.currentTimeMillis() - start) + "ms"); + + return result; + } + + public abstract Collection load(Collection keys); + + protected Long getLong(ResultSet rs, String columnName) throws SQLException { + return rs.getObject(columnName, Long.class); + } + + protected Instant getInstantFromTimestamp(ResultSet rs, String column) throws SQLException { + Timestamp date = rs.getTimestamp(column); + return date != null ? date.toInstant() : null; + } + +/* todo реfакторинг: + protected LegalEntityProfile getLegalEntityProfile(Long id) { + if (id == null) + return null; + List results = jdbcTemplate.query( + "SELECT * FROM legalentityprofile WHERE id = ?", new Object[]{id}, + (rs, rowNum) -> { + LegalEntityProfile legalEntityProfile = new LegalEntityProfile(); + legalEntityProfile.setDescription(rs.getString("description")); + legalEntityProfile.setDomicileCountryCodeId(rs.getObject("domicilecountrycodeid", Long.class)); + legalEntityProfile.setProfessional(rs.getObject("isprofessional", Boolean.class)); + legalEntityProfile.setOrganizationTypeId(rs.getObject("organizationtypeid", Long.class)); + legalEntityProfile.setOgrn(rs.getString("ogrn")); + legalEntityProfile.setShortName(rs.getString("name")); + legalEntityProfile.setFullName(rs.getString("fullname")); + legalEntityProfile.setNameEng(rs.getString("nameeng")); + legalEntityProfile.setFullNameEng(rs.getString("fullnameeng")); + legalEntityProfile.setKpp(rs.getString("kpp")); + legalEntityProfile.setContactPhone(rs.getString("contactphone")); + legalEntityProfile.setContactEMail(rs.getString("contactemail")); + legalEntityProfile.setTsedAllowedId(rs.getObject("tsedallowedid", Long.class)); + legalEntityProfile.setEdoMail(rs.getString("edomail")); + legalEntityProfile.setNsdAllowedId(rs.getObject("nsdallowedid", Long.class)); + legalEntityProfile.setOtcMonitorAllowedId(rs.getObject("otcmonitorallowedid", Long.class)); + legalEntityProfile.setOtcMonitorObligedId(rs.getObject("otcmonitorobligedid", Long.class)); + legalEntityProfile.setId(getLong(rs, "id")); + return legalEntityProfile; + } + ); + LegalEntityProfile onceObject = DataAccessUtils.singleResult(results); + if (onceObject == null) { + log.warn("No row in table legalentityprofile where id={}. Create empty ", id); + onceObject = new LegalEntityProfile(); + onceObject.setId(id); // чтобы не потерять id + } + return onceObject; + } +*/ + +// /** +// * Без ID перечисление значений полей LegalEntityProfile. +// * См. LegalentityprofileStatementArgument +// * +// * @param legalEntityProfile +// * @return +// */ +// protected List legalEntityProfileToSQLArgs(@NonNull LegalEntityProfile legalEntityProfile) { +// List legalEntityArgs = new ArrayList<>(Arrays.asList( +//// legalEntityProfile.getId(), +// legalEntityProfile.getDescription(), +// legalEntityProfile.getDomicileCountryCodeId(), +// legalEntityProfile.getProfessional(), +// legalEntityProfile.getOrganizationTypeId(), +// legalEntityProfile.getOgrn(), +// legalEntityProfile.getShortName(), +// legalEntityProfile.getFullName(), +// legalEntityProfile.getNameEng(), +// legalEntityProfile.getFullNameEng(), +// legalEntityProfile.getKpp(), +// legalEntityProfile.getContactPhone(), +// legalEntityProfile.getContactEMail(), +// legalEntityProfile.getTsedAllowedId(), +// legalEntityProfile.getEdoMail(), +// legalEntityProfile.getNsdAllowedId(), +// legalEntityProfile.getOtcMonitorAllowedId(), +// legalEntityProfile.getOtcMonitorObligedId() +// )); +// return legalEntityArgs; +// } + +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/base/SimpleObjectMapStore.java b/storage/src/main/java/ru/spcex/clearing/storage/base/SimpleObjectMapStore.java new file mode 100644 index 000000000..8d45ff45c --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/base/SimpleObjectMapStore.java @@ -0,0 +1,200 @@ +package ru.spcex.clearing.storage.base; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.SqlTypeValue; +import org.springframework.jdbc.core.StatementCreatorUtils; +import org.springframework.lang.NonNull; +import ru.clearing.classes.objects.ObjectBase; +import ru.spcex.clearing.storage.utils.BigDecimalUtil; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.*; + +/** + * MapStore для выгрузки обычных объектов + * + * @param + */ +public abstract class SimpleObjectMapStore extends LoggingMapStore +{ + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + + protected SimpleDateFormat FIREBIRD_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd"); // не делать static. + protected final JdbcTemplate jdbcTemplate; + private final int bathSize = 1000; + + protected SimpleObjectMapStore(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + logHeaders(getFields()); + } + + public abstract String getTableName(); + + public abstract String[] getFields(); + + private boolean oidIsPresentInTheFields = Arrays.stream(getFields()).anyMatch(anotherString -> { + if (anotherString == null) return false; + return "oid".equalsIgnoreCase(anotherString.trim()); + }); + + public boolean deleteIsSupported() { + return !oidIsPresentInTheFields; + } + + @Override + public final void store(Long id, T o) { + log.debug("store {} {} {}", getTableName(), id, o); + Map map = new HashMap<>(); + map.put(id, o); + store(map); + } + + @Override + public final void storeAll(Map map) { + log.debug("storeAll {} {}", getTableName(), map); + store(map); + } + + public abstract void store(Map map); + + @Override + public void delete(Long id) { + if (deleteIsSupported()) { + defaultDelete(id); + } else { + throw new UnsupportedOperationException("delete not supported"); + } + } + + @Override + public void deleteAll(Collection collection) { + if (deleteIsSupported()) { + deleteAllShowNotDeleted(collection); + } else { + throw new UnsupportedOperationException("deleteAll not supported"); + } + } + + protected void defaultDelete(Long id) { + ArrayList list = new ArrayList<>(); + list.add(id); + deleteAll(list); + } + + protected void deleteAllShowNotDeleted(Collection collection) { + List listOfID = new ArrayList<>(collection.size()); + for (Long id : collection) + listOfID.add(new Object[]{id}); + try { + jdbcTemplate.batchUpdate("DELETE FROM " + getTableName() + " WHERE id=?", listOfID); + } catch (Exception e) { + log.error("SQL error at batch DELETE FROM {}\n{}", getTableName(), ExceptionUtils.getStackTrace(e)); + log.info("List of non deleted ID {}", collection); // показать список неудалённых UUID + throw e; + } + } + + @Override + public abstract T load(Long id); + + @Override + public Map loadAll(Collection keys) { + Map result = new HashMap<>(); + for (Long key : keys) { + result.put(key, load(key)); + } + return result; + } + + @Override + public Iterable loadAllKeys() { + log.debug("loadAllKeys from " + getTableName()); + List keys; + try { + keys = jdbcTemplate.query("select id from " + getTableName(), (resultSet, i) -> resultSet.getLong("id")); + } catch (Throwable e) { + log.error("{}", ExceptionUtils.getStackTrace(e)); + throw e; + } + return keys; + } + + /** + * Загружает ключи только за текущий день. + * where CAST(tradingday as DATE) = ? + * Осторожно со знаком: в некоторых случаях требуется за текущий и будущие дни, тогда этот метод не подходит. + * См. так же loadAllKeys(). + * + * @return список id + */ + protected List defaultLoadAllKeysOnTradingDay() { + log.debug("loadAllKeys from " + getTableName()); + return jdbcTemplate.query("select id from " + getTableName() + " where CAST(tradingday as DATE) = ?", + new Object[]{FIREBIRD_DATE_FORMATTER.format(new Date())}, + (resultSet, i) -> resultSet.getObject("id", Long.class)); + } + + + protected void batchInsertUpdate(@NonNull String insertStatement, @NonNull List args) { + try { + logValues(args); + int[][] ret = jdbcTemplate.batchUpdate(insertStatement, args, bathSize, (ps, params) -> { + for (int i = 0; i < params.length; i++) { + int type = ps.getParameterMetaData().getParameterType(i + 1); + Object value = params[i]; + if (value != null) { + if ((type == Types.VARCHAR || type == Types.CHAR) && value instanceof String) { + String stringValue = (String) value; + int length = ps.getParameterMetaData().getPrecision(i + 1); + if (stringValue.length() > length) { + value = stringValue.substring(0, length); + log.error("String value for insert is too large [ insertSql: {}, params: {}, incorrect value : {}, normalized value: {} ]", + insertStatement, Arrays.toString(params), stringValue, value); + } + } else if ((type == Types.DECIMAL || type == Types.NUMERIC) && value instanceof BigDecimal) { + BigDecimal bigDecimalValue = (BigDecimal) value; + int dbScalePartLength = ps.getParameterMetaData().getScale(i + 1); + int precision = ps.getParameterMetaData().getPrecision(i + 1); + if (!BigDecimalUtil.checkSize(bigDecimalValue, precision, dbScalePartLength)) { + value = BigDecimalUtil.genMaxValueForInsert(precision, dbScalePartLength); + log.error("BigDecimal value for insert is too large [ insertSql: {}, params: {}, incorrect value : {}, normalized value: {} ]", + insertStatement, Arrays.toString(params), bigDecimalValue, value); + } + } + } + //в batchUpdate(String sql, List batchArgs), как типа поля всегда передавался SqlTypeValue.TYPE_UNKNOWN, + //см. BatchUpdateUtils.setStatementParameters + try { + StatementCreatorUtils.setParameterValue(ps, i + 1, SqlTypeValue.TYPE_UNKNOWN, value); + } catch (SQLException e) { + throw new SQLException("batchInsertUpdate, at prepare argument " + i + " value = " + value + "; SQL=" + insertStatement, e); + } + } + }); + int batchSize = ret.length > 0 ? ret[0].length : ret.length; + log.debug("{} {} rows stored", getTableName(), batchSize); + } catch (Exception e) { + log.error("SQL error at batch UPDATE OR INSERT INTO {}\n{}", getTableName(), ExceptionUtils.getStackTrace(e)); + throw e; + } + } + + protected Instant getInstantFromTimestamp(ResultSet rs, String column) throws SQLException { + Timestamp date = rs.getTimestamp(column); + return date != null ? date.toInstant() : null; + } + + protected Timestamp timestampFromInstant(Instant instant) { + if (instant == null) return null; + return Timestamp.from(instant); + } +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/config/ConfigurationRootElement.java b/storage/src/main/java/ru/spcex/clearing/storage/config/ConfigurationRootElement.java new file mode 100644 index 000000000..75dc99b8a --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/config/ConfigurationRootElement.java @@ -0,0 +1,48 @@ +package ru.spcex.clearing.storage.config; + + +/** + * Корневой элемент конфигурации + */ + +@SuppressWarnings("DefaultAnnotationParam") +public class ConfigurationRootElement /*extends ConfigurationRootBaseElement*/ { + + /** + * Конфигурация БД + */ +// @JsonProperty(value = "Database", required = true) + private SettingsElementDatabase database = new SettingsElementDatabase(); + +// @JsonProperty(value = "Settings", required = false) + private SettingsElement settings = new SettingsElement(); + + /** + * Конфигурация Hazelcast + */ +// @JsonProperty(value = "HazelcastServer", required = true) +// private HazelcastServerElement hazelcast = new HazelcastServerElement(); + + /** + * Конфигурация сервисов Core + */ +// @JsonProperty(value = "Services") + private ServicesElement servicesElement = new ServicesElement(); + + public SettingsElementDatabase getDatabase() { + return database; + } + +// public HazelcastServerElement getHazelcast() { +// return hazelcast; +// } + + public SettingsElement getSettings() { + return settings; + } + + public ServicesElement getServicesElement() { + return servicesElement; + } + +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/config/DbConnectionConfig.java b/storage/src/main/java/ru/spcex/clearing/storage/config/DbConnectionConfig.java new file mode 100644 index 000000000..233157da2 --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/config/DbConnectionConfig.java @@ -0,0 +1,72 @@ +package ru.spcex.clearing.storage.config; + +import com.mchange.v2.c3p0.ComboPooledDataSource; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import ru.spcex.clearing.storage.error.ModuleInitializeException; +import ru.spcex.clearing.storage.utils.DbDefaultConfig; + +import javax.sql.DataSource; +import java.sql.Connection; + +@SuppressWarnings("UnnecessaryLocalVariable") +@Configuration +public class DbConnectionConfig { + private Logger log = LoggerFactory.getLogger(this.getClass()); + + private ConfigurationRootElement configRoot = DfaConfig.get().getRoot(); + + @Bean + public DataSource dataSource() /*fixme why: throws PropertyVetoException*/ { + DataSource result; + String login = configRoot.getDatabase().getLogin(); + String password = configRoot.getDatabase().getPassword(); + String logTimeoutPart = ""; + String dbPath; + if (StringUtils.isEmpty(configRoot.getDatabase().getEmbeddedFilePath())) { + dbPath = configRoot.getDatabase().getJdbcConnectionString(); + int timeoutSec = configRoot.getDatabase().getConnectionAcquireTimeoutSeconds(); + ComboPooledDataSource cpds = new ComboPooledDataSource(); + try { + cpds.setDriverClass(configRoot.getDatabase().getDriver()); + } catch (Exception ue) { // fixme не компилится из-за PropertyVetoException + throw new RuntimeException(ue); + } + cpds.setJdbcUrl(dbPath); + cpds.setUser(login); + cpds.setPassword(password); + cpds.setInitialPoolSize(configRoot.getDatabase().getMinPoolSize()); + cpds.setMinPoolSize(configRoot.getDatabase().getMinPoolSize()); + cpds.setMaxPoolSize(configRoot.getDatabase().getMaxPoolSize()); + cpds.setNumHelperThreads(configRoot.getDatabase().getNumHelperThreads()); + cpds.setCheckoutTimeout(timeoutSec * 1000/*todo common 1000==ConstsCommon.SECOND*/); + logTimeoutPart = String.format(" (timeout=%ds)", timeoutSec); + result = cpds; + } else { + dbPath = configRoot.getDatabase().getEmbeddedFilePath(); + result = DbDefaultConfig.getEmbeddedDatabase(dbPath); + } + String OPERATION_DATABASE_CONNECTION_CHECK = String.format("Database [%s] connection check", dbPath); + try { + Connection conn = result.getConnection(); + conn.close(); + log.info("{}: success", OPERATION_DATABASE_CONNECTION_CHECK); + return result; + } catch (Throwable e) { + String msg = String.format("%s%s: failed: %s -> %s", + OPERATION_DATABASE_CONNECTION_CHECK, logTimeoutPart, e.getClass().getSimpleName(), e.getMessage()); + log.error(msg); + throw new ModuleInitializeException(msg, e); + } + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + return jdbcTemplate; + } +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/config/DfaConfig.java b/storage/src/main/java/ru/spcex/clearing/storage/config/DfaConfig.java new file mode 100644 index 000000000..dde133d1a --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/config/DfaConfig.java @@ -0,0 +1,34 @@ +package ru.spcex.clearing.storage.config; + + +public class DfaConfig { + protected static DfaConfig self; + + + ConfigurationRootElement root = new ConfigurationRootElement(); + + protected String defaultLogFileName() { + return "storage"; + } + + + public String appName() { + return "clearing-storage"; + } + + + protected Class parsedClazz() { + return ConfigurationRootElement.class; + } + + public static DfaConfig get() { + if (self == null) { + self = new DfaConfig(); + } + return self; + } + + public ConfigurationRootElement getRoot() { + return root; + } +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/config/HazelcastConfiguration.java b/storage/src/main/java/ru/spcex/clearing/storage/config/HazelcastConfiguration.java new file mode 100644 index 000000000..91995dd4a --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/config/HazelcastConfiguration.java @@ -0,0 +1,71 @@ +package ru.spcex.clearing.storage.config; + +import com.hazelcast.config.*; +import com.hazelcast.core.HazelcastInstance; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastInstanceFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class HazelcastConfiguration { + + private ConfigurationRootElement configRoot = DfaConfig.get().getRoot(); + private final PoolMapConfigs poolMapConfigs; + + @Autowired + public HazelcastConfiguration(PoolMapConfigs poolMapConfigs) { + this.poolMapConfigs = poolMapConfigs; + } + + @Bean + public HazelcastInstance hazelcastServerInstance(Config config) { + return (new HazelcastInstanceFactory(config)).getHazelcastInstance(); + } + + @Bean + public Config hazelCastConfig() { + Config config = new Config(); + config.setInstanceName("instance"); + config.setGroupConfig(new GroupConfig() +// todo config .setName(configRoot.getHazelcast().getLogin()) +// .setPassword(configRoot.getHazelcast().getPassword() +// ) + ); +// if (StringUtils.isEmpty(configRoot.getHazelcast().getMancenterUrl())) { +// config.setManagementCenterConfig(new ManagementCenterConfig() +// .setEnabled(false) +// ); +// } else { +// config.setManagementCenterConfig(new ManagementCenterConfig() +// .setEnabled(true) +// .setUrl(configRoot.getHazelcast().getMancenterUrl()) +// ); +// } + config.setProperty("hazelcast.shutdownhook.enabled", "true"); + config.setProperty("hazelcast.logging.type", "slf4j"); + config.setProperty("hazelcast.operation.call.timeout.millis", "600000"); + config.setNetworkConfig(new NetworkConfig() +// .setPort(configRoot.getHazelcast().getListenPort()) + .setJoin(new JoinConfig() + .setMulticastConfig(new MulticastConfig() + .setEnabled(false)) + .setTcpIpConfig(new TcpIpConfig() + .setEnabled(true) +// .setMembers( +// DfaConfig.get() +// .getRoot().getHazelcast().getClusterMembersList() +// ) + ) + ) + ); + + config.addMapConfig(poolMapConfigs.map_WorkflowStatusDictionary()); + config.addMapConfig(poolMapConfigs.map_WorkflowStatusDictionary()); + + return config; + } + + +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/config/PlatformStorageBeans.java b/storage/src/main/java/ru/spcex/clearing/storage/config/PlatformStorageBeans.java new file mode 100644 index 000000000..a43a90752 --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/config/PlatformStorageBeans.java @@ -0,0 +1,9 @@ +package ru.spcex.clearing.storage.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan(basePackages = "com.moex.platform.storage") +public class PlatformStorageBeans { +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/config/PoolMapConfigs.java b/storage/src/main/java/ru/spcex/clearing/storage/config/PoolMapConfigs.java new file mode 100644 index 000000000..5202ef17d --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/config/PoolMapConfigs.java @@ -0,0 +1,74 @@ +package ru.spcex.clearing.storage.config; + +import com.hazelcast.config.*; +import com.hazelcast.core.MapLoader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import ru.spcex.clearing.storage.dictionary.WorkflowStatusDictionaryMapStore; +import ru.spcex.clearing.storage.object.CompanyRoleSetMapStore; +import ru.spcex.clearing.storage.utils.IMDGDistributedNames; + +@Configuration +public class PoolMapConfigs { + + private ConfigurationRootElement configRoot = DfaConfig.get().getRoot(); + + public PoolMapConfigs() { + } + + private MapStoreConfig makeDefaultMapStoreConfig(MapLoader mapBean) { + return new MapStoreConfig() + .setImplementation(mapBean) +;//todo config: .setWriteDelaySeconds(configRoot.getHazelcast().getDbSyncSeconds()); + } + + public ScheduledExecutorConfig makeDefaultScheduledExecutorConfig(String name) { + return new ScheduledExecutorConfig() + .setName(name) + .setPoolSize(16) + .setDurability(1) + .setCapacity(0); + } + + public MapConfig makeDefaultMapConfig(String mapName, MapLoader mapBean) { + return new MapConfig() + .setName(mapName) + .setInMemoryFormat(InMemoryFormat.OBJECT) + .setMapStoreConfig(makeDefaultMapStoreConfig(mapBean)) +//fixme config: .setBackupCount(configRoot.getHazelcast().getBackupCount()) + ; + } + + private NearCacheConfig makeDefaultNearCacheConfig() { + return new NearCacheConfig() + .setMaxIdleSeconds(3600) + .setInMemoryFormat(InMemoryFormat.OBJECT) + .setSerializeKeys(true) + ; + } + + private MapIndexConfig makeMapIndexConfig(String attributeName, boolean ordered) { + return new MapIndexConfig() + .setAttribute(attributeName) + .setOrdered(ordered); + } + + + + + @Autowired + private WorkflowStatusDictionaryMapStore workflowStatusDictionaryMapStore; + + public MapConfig map_WorkflowStatusDictionary() { + return makeDefaultMapConfig(IMDGDistributedNames.Map_WorkflowStatusDictionary, workflowStatusDictionaryMapStore) + .setNearCacheConfig(makeDefaultNearCacheConfig()); + } + + @Autowired + private CompanyRoleSetMapStore companyRoleSetMapStore; + + public MapConfig map_CompanyRoleSet() { + return makeDefaultMapConfig(IMDGDistributedNames.Map_CompanyRoleSet, companyRoleSetMapStore); + } + +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/config/ServicesElement.java b/storage/src/main/java/ru/spcex/clearing/storage/config/ServicesElement.java new file mode 100644 index 000000000..75e56a89a --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/config/ServicesElement.java @@ -0,0 +1,34 @@ +package ru.spcex.clearing.storage.config; + +//import com.fasterxml.jackson.annotation.JsonFormat; +//import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; +import java.util.Date; + +/** + * Настройка сервисов + */ +public class ServicesElement implements Serializable { + + /** + * Время, через которое выставленная заявка будет снята, в миллисекундах. + */ +// @JsonProperty(value = "ExpirationDelay") + private Integer ExpirationDelay = 5 * 60 * 1000; + + public Integer getExpirationDelay() { + return ExpirationDelay; + } + + /** + * Время окончания торговой сессии + */ +// @JsonProperty(value = "SessionEndTime") +// @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss", timezone = "Europe/Moscow") + private Date SessionEndTime = null; + + public Date getSessionEndTime() { + return SessionEndTime; + } +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/config/SettingsElement.java b/storage/src/main/java/ru/spcex/clearing/storage/config/SettingsElement.java new file mode 100644 index 000000000..d2dbe8077 --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/config/SettingsElement.java @@ -0,0 +1,29 @@ +package ru.spcex.clearing.storage.config; + +//import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; + +@SuppressWarnings({"DefaultAnnotationParam", "unused", "FieldCanBeLocal"}) +public class SettingsElement implements Serializable { +// @JsonProperty(value = "InitHazelcastThreadMultiplier", required = false) + private int initHazelcastThreadMultiplier = 2; + +// @JsonProperty(value = "RecoveryLogPath", required = false) + private String recoveryLogPath; + +// @JsonProperty(value = "ExecutionLimitationPeriod", required = false) + private Integer executionLimitationPeriod = 10; + + public int getInitHazelcastThreadMultiplier() { + return initHazelcastThreadMultiplier; + } + + public Integer getExecutionLimitationPeriod() { + return executionLimitationPeriod; + } + + public String getRecoveryLogPath() { + return recoveryLogPath; + } +} \ No newline at end of file diff --git a/storage/src/main/java/ru/spcex/clearing/storage/config/SettingsElementDatabase.java b/storage/src/main/java/ru/spcex/clearing/storage/config/SettingsElementDatabase.java new file mode 100644 index 000000000..5f9a5855f --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/config/SettingsElementDatabase.java @@ -0,0 +1,89 @@ +package ru.spcex.clearing.storage.config; + +//import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; + +@SuppressWarnings({"FieldCanBeLocal", "unused", "DefaultAnnotationParam"}) +public class SettingsElementDatabase implements Serializable { + +// @JsonProperty(value = "Driver") + private String driver = "org.firebirdsql.jdbc.FBDriver"; + +// @JsonProperty(value = "JdbcConnectionString", required = true) + private String jdbcConnectionString; + +// @JsonProperty(value = "Login", required = true) + private String login; + +// @JsonProperty(value = "Password", required = true) + private String password; + +// @JsonProperty(value = "MaxPoolSize") + private int maxPoolSize = 30; + +// @JsonProperty(value = "MinPoolSize") + private int minPoolSize = 10; + +// @JsonProperty(value = "EmbeddedFilePath", required = false) + private String embeddedFilePath; + +// @JsonProperty(value = "NumHelperThreads", required = false) + private int numHelperThreads = Runtime.getRuntime().availableProcessors() * 2; + +// @JsonProperty(value = "ConnectionAcquireTimeoutSeconds", required = false) + private int connectionAcquireTimeoutSeconds = 30; + + public String getDriver() { + return driver; + } + + public String getJdbcConnectionString() { + return jdbcConnectionString; + } + + public String getLogin() { + return login; + } + + public String getPassword() { + return password; + } + + public int getMaxPoolSize() { + return maxPoolSize; + } + + public int getMinPoolSize() { + return minPoolSize; + } + + public String getEmbeddedFilePath() { + return embeddedFilePath; + } + + public int getNumHelperThreads() { + return numHelperThreads; + } + + public int getConnectionAcquireTimeoutSeconds() { + return connectionAcquireTimeoutSeconds; + } + + // для поддержки интеграционных тестов otc-test: + public void setJdbcConnectionString(String jdbcConnectionString) { + this.jdbcConnectionString = jdbcConnectionString; + } + + public void setLogin(String login) { + this.login = login; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setEmbeddedFilePath(String embeddedFilePath) { + this.embeddedFilePath = embeddedFilePath; + } +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/dictionary/WorkflowStatusDictionaryMapStore.java b/storage/src/main/java/ru/spcex/clearing/storage/dictionary/WorkflowStatusDictionaryMapStore.java new file mode 100644 index 000000000..d807e1842 --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/dictionary/WorkflowStatusDictionaryMapStore.java @@ -0,0 +1,24 @@ +package ru.spcex.clearing.storage.dictionary; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; +import ru.clearing.platform.dictionary.WorkflowStatusDictionary; +import ru.spcex.clearing.storage.base.DictionaryMapStore; + +@Component +public class WorkflowStatusDictionaryMapStore extends DictionaryMapStore { + + public WorkflowStatusDictionaryMapStore(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + public String getTableName() { + return "WORKFLOW_STATUS_DICTIONARY"; + } + + @Override + public WorkflowStatusDictionary getDictionaryObject() { + return new WorkflowStatusDictionary(); + } +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/error/ModuleInitializeException.java b/storage/src/main/java/ru/spcex/clearing/storage/error/ModuleInitializeException.java new file mode 100644 index 000000000..0e40d7936 --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/error/ModuleInitializeException.java @@ -0,0 +1,19 @@ +package ru.spcex.clearing.storage.error; + +public class ModuleInitializeException extends RuntimeException { + public ModuleInitializeException() { + } + + public ModuleInitializeException(String message) { + super(message); + } + + public ModuleInitializeException(String message, Throwable cause) { + super(message, cause); + } + + public ModuleInitializeException(Throwable cause) { + super(cause); + } + +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/object/CompanyRoleSetMapStore.java b/storage/src/main/java/ru/spcex/clearing/storage/object/CompanyRoleSetMapStore.java new file mode 100644 index 000000000..43e397681 --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/object/CompanyRoleSetMapStore.java @@ -0,0 +1,61 @@ +package ru.spcex.clearing.storage.object; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; +import ru.clearing.classes.StaticData.Company.Company; +import ru.clearing.classes.StaticData.Company.CompanyRoleSet; +import ru.spcex.clearing.storage.base.ObjectBaseMapStore; +import ru.spcex.clearing.storage.utils.DbUtilsHelper; + +import java.util.*; + +@Component +public class CompanyRoleSetMapStore extends ObjectBaseMapStore { + + public CompanyRoleSetMapStore(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + public String getTableName() { + return "COMPANY_ROLE_SET"; + } + + @Override + public String[] getFields() { + return new String[]{"id", "roleid", "companyid"}; + } + + protected final String insertStatement = DbUtilsHelper.createUpdateOrInsert(getTableName(), getFields(), "id"); + + @Override + public Collection load(Collection keys) { + Map> paramMap = Collections.singletonMap("ids", keys); + return namedParameterJdbcTemplate.query("select * from " + getTableName() + " where id in (:ids)", paramMap, + (resultSet, i) -> { + CompanyRoleSet companyRoleSet = new CompanyRoleSet(); + companyRoleSet.setId(resultSet.getObject("id", Long.class)); + companyRoleSet.setRoleId(resultSet.getObject("roleid", Long.class)); + companyRoleSet.setCompanyId(resultSet.getObject("companyid", Long.class)); + return companyRoleSet; + }); + } + + @Override + public void store(Map map) { + List batchArgs = new ArrayList<>(); + + for (Map.Entry entry : map.entrySet()) { + CompanyRoleSet partnerList = entry.getValue(); + + Object[] args = new Object[]{ + partnerList.getId(), + partnerList.getRoleId(), + partnerList.getCompanyId(), + }; + batchArgs.add(args); + } + batchInsertUpdate(insertStatement, batchArgs); + } + +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/utils/BigDecimalUtil.java b/storage/src/main/java/ru/spcex/clearing/storage/utils/BigDecimalUtil.java new file mode 100644 index 000000000..4907c447d --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/utils/BigDecimalUtil.java @@ -0,0 +1,111 @@ +package ru.spcex.clearing.storage.utils; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; + +public class BigDecimalUtil { + + private static final DecimalFormat defaultDecimalFormat; + + static { + DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(); + decimalFormatSymbols.setDecimalSeparator('.'); + decimalFormatSymbols.setGroupingSeparator(' '); + defaultDecimalFormat = new DecimalFormat("#,###.00", decimalFormatSymbols); + } + + public static BigDecimal sum(BigDecimal num1, BigDecimal num2) { + BigDecimal sum = BigDecimal.ZERO; + if (num1 != null) + sum = sum.add(num1); + if (num2 != null) + sum = sum.add(num2); + return sum; + } + + /** + * @param value + * @param dbSize общее кол-во позиций + * @param dbScale кол-во позиций под дробную часть + * @return true, если размер целой части не превышает значения, указанные в параметрах + */ + public static boolean checkSize(BigDecimal value, int dbSize, int dbScale) { + if (value == null) + return true; + return value.precision() - value.scale() <= dbSize - dbScale; + } + + public static BigDecimal genMaxValueForInsert(int dbSize, int dbScale) { + int maxIntSizeForInsert = dbSize - dbScale; + long newLongValue = new BigDecimal(Math.pow(10, maxIntSizeForInsert)).longValue(); + return new BigDecimal(newLongValue - 1); + } + + public static BigDecimal chooseHighest(BigDecimal oldPrice, BigDecimal newPrice) { + if (newPrice != null && (oldPrice == null || isLess(oldPrice, newPrice))) { + return newPrice; + } else { + return oldPrice; + } + } + + public static BigDecimal chooseSmallest(BigDecimal oldPrice, BigDecimal newPrice) { + if (newPrice != null && (oldPrice == null || isLess(newPrice, oldPrice))) { + return newPrice; + } else { + return oldPrice; + } + } + + /** + * Если bestPrice меньше price, то true, иначе false + * + * @param bestPrice + * @param price + * @return + */ + public static boolean isLess(BigDecimal bestPrice, BigDecimal price) { + return price != null && bestPrice != null && bestPrice.compareTo(price) < 0; + } + + public static Integer nullSafeAdd(Integer a, Integer b) { + if (a == null) { + return b; + } else if (b == null) { + return a; + } else { + return a + b; + } + } + + public static BigDecimal nullSafeAdd(BigDecimal a, BigDecimal b) { + if (a == null) { + return b; + } else if (b == null) { + return a; + } else { + return a.add(b); + } + } + + public static Long nullSafeAdd(Long a, Long b) { + if (a == null) { + return b; + } else if (b == null) { + return a; + } else { + return a + b; + } + } + + public static String defaultFormatValue(BigDecimal val) { + DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(); + decimalFormatSymbols.setDecimalSeparator('.'); + decimalFormatSymbols.setGroupingSeparator(' '); + if (val == null) { + return ""; + } + return defaultDecimalFormat.format(val); + } +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/utils/DbDefaultConfig.java b/storage/src/main/java/ru/spcex/clearing/storage/utils/DbDefaultConfig.java new file mode 100644 index 000000000..644f937ee --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/utils/DbDefaultConfig.java @@ -0,0 +1,11 @@ +package ru.spcex.clearing.storage.utils; + + +import javax.sql.DataSource; +import java.nio.file.Paths; + +public class DbDefaultConfig { + public static DataSource getEmbeddedDatabase(String embeddedFilePath) { + throw new UnsupportedOperationException("Are PostgreSQL not supported embedded DB?");//todo PsotgreSQL + } +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/utils/DbUtilsHelper.java b/storage/src/main/java/ru/spcex/clearing/storage/utils/DbUtilsHelper.java new file mode 100644 index 000000000..b535bc64f --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/utils/DbUtilsHelper.java @@ -0,0 +1,51 @@ +package ru.spcex.clearing.storage.utils; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class DbUtilsHelper { + + private static String generateValuesBlock(int querySignCount) { + return StringUtils.rightPad("(?", querySignCount * 3 - 1, ", ?") + ")"; + } + + private static Pattern pGetColumns = Pattern.compile("(?m)(?i)INTO\\s+[0-9a-z\" _$]+\\s+\\(\\s*([0-9a-z_\\- ,\"\\n+]*?)\\s*\\)"); + + public static String fillValuesBlock(String insertStatement) { + + Matcher m = pGetColumns.matcher(insertStatement); + if (m.find()) { + String g = m.group(1); + String block = generateValuesBlock(g.split(",").length); + return insertStatement.replaceAll("%values_block%", block); + } else { + return insertStatement; + } + } + + public static String createInsert(String tableName, String[] fields) { + return "INSERT INTO " + tableName + + " (" + Arrays.stream(fields).map(f -> "\"" + f.toUpperCase() + "\"").collect(Collectors.joining(", ")) + ") values (" + + // Arrays.stream(fields).map(f -> "\"" + f.toUpperCase() + "\"").collect(Collectors.joining(", ", ":", "")); + String.join(", ", Collections.nCopies(fields.length, "?")) + + ")"; + } + + public static String createUpdateOrInsert(String tableName, String[] fields, String matchingKey) { + String updateOrInsert = "INSERT INTO " + tableName + + " (" + Arrays.stream(fields).map(f -> "\"" + f.toUpperCase() + "\"").collect(Collectors.joining(", ")) + ") values (" + + // Arrays.stream(fields).map(f -> "\"" + f.toUpperCase() + "\"").collect(Collectors.joining(", ", ":", "")); + String.join(", ", Collections.nCopies(fields.length, "?")) + + ") WHERE "; + if (matchingKey != null) // PostgreSQL conflict_target fixme проверить мэтчинг + updateOrInsert += " " + matchingKey.toUpperCase(); + updateOrInsert += " CONFLICT DO UPDATE"; + + return updateOrInsert; + } +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/utils/IMDGDistributedNames.java b/storage/src/main/java/ru/spcex/clearing/storage/utils/IMDGDistributedNames.java new file mode 100644 index 000000000..0add1d9db --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/utils/IMDGDistributedNames.java @@ -0,0 +1,10 @@ +package ru.spcex.clearing.storage.utils; + +//fixme перенести в новый модуль IMDGDistributedNames +public final class IMDGDistributedNames { + private IMDGDistributedNames() { + } + + public static final String Map_WorkflowStatusDictionary = "Map_WorkflowStatusDictionary"; + public static final String Map_CompanyRoleSet = "Map_CompanyRoleSet"; +} diff --git a/storage/src/main/java/ru/spcex/clearing/storage/utils/TimeUtil.java b/storage/src/main/java/ru/spcex/clearing/storage/utils/TimeUtil.java new file mode 100644 index 000000000..3059f26ec --- /dev/null +++ b/storage/src/main/java/ru/spcex/clearing/storage/utils/TimeUtil.java @@ -0,0 +1,288 @@ +package ru.spcex.clearing.storage.utils; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; + +/** + * Для работы с java.util.Date + * И конвертация в новый формат + * todo копипаст юниттеста нужен. + */ +public class TimeUtil { + private static final Logger log = LoggerFactory.getLogger(TimeUtil.class); + public static final ZoneId zone = ZoneId.systemDefault(); + + /** + * Получить дату без времени (начало дня) + * + * @param date + * @return + */ + public static Date getDateOnStartOfDay(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } + + /** + * Получить начало следующего дня + * + * @param date + * @return + */ + public static Date getDateNextDay(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + calendar.add(Calendar.DAY_OF_YEAR, 1); + return calendar.getTime(); + } + + public static LocalDateTime toDateTime(Date fromDate) { + if (fromDate == null) + return null; + if (fromDate instanceof java.sql.Timestamp) + return ((java.sql.Timestamp) fromDate).toLocalDateTime(); + if (fromDate instanceof java.sql.Date || fromDate instanceof java.sql.Time) + log.debug("Warning at converting date/time: for class {} may not contain date or time. unixtime={}", fromDate.getClass(), fromDate.getTime()); + return toDateTime0(fromDate); + } + + private static LocalDateTime toDateTime0(Date fromDate) { + try { + if (fromDate instanceof java.sql.Date) { + Instant instant = Instant.now(); //can be LocalDateTime + ZoneId systemZone = ZoneId.systemDefault(); // my timezone + ZoneOffset currentOffsetForMyZone = systemZone.getRules().getOffset(instant); + return LocalDateTime.ofEpochSecond(fromDate.getTime() / 1000, 0, currentOffsetForMyZone); + } + return LocalDateTime.ofInstant(fromDate.toInstant(), ZoneId.systemDefault()); + } catch (UnsupportedOperationException ue) { + log.warn("Error(warn) convert type of date/time: {}({}) class: {}. Retry", fromDate, fromDate.getTime(), fromDate.getClass()); + try { + Instant instant = Instant.now(); //can be LocalDateTime + ZoneId systemZone = ZoneId.systemDefault(); // my timezone + ZoneOffset currentOffsetForMyZone = systemZone.getRules().getOffset(instant); + return LocalDateTime.ofEpochSecond(fromDate.getTime() / 1000, 0, currentOffsetForMyZone); + } catch (Exception e) { + log.error("Error convert type2 of date/time: {} class: {}\n{}", fromDate, fromDate.getClass(), ExceptionUtils.getStackTrace(ue)); + return null; + } + } +// Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); + } + + public static LocalDate toDate(Date fromDate) { + if (fromDate == null) + return null; + if (fromDate instanceof java.sql.Date) + return ((java.sql.Date) fromDate).toLocalDate(); + if (fromDate instanceof java.sql.Time) + log.debug("Warning at converting date/time: for class {} may not contain date. unixtime={}", fromDate.getClass(), fromDate.getTime()); + return toDateTime0(fromDate).toLocalDate(); + } + + public static LocalTime toTime(Date fromDate) { + if (fromDate == null) + return null; + if (fromDate instanceof java.sql.Time) + return ((java.sql.Time) fromDate).toLocalTime(); + if (fromDate instanceof java.sql.Date) + log.debug("Warning at converting date/time: for class {} may not contain time. unixtime={}", fromDate.getClass(), fromDate.getTime()); + return toDateTime0(fromDate).toLocalTime(); + } + + /** + * Последовательный ряд дат дней между двумя датами. + * + * @param from с даты, включительно + * @param to по дату, включительно. + * @return + */ + public static Date[] makeDateRange(Date from, Date to) { + ArrayList datesL = new ArrayList(); + LocalDate fromDate = toDate(from); + LocalDate toDate = toDate(to); + datesL.add(from); + for (LocalDate dateI = fromDate.plusDays(1); dateI.isBefore(toDate); dateI = dateI.plusDays(1)) { + Date date = Date.from(dateI.atStartOfDay(ZoneId.systemDefault()).toInstant()); + datesL.add(date); + } + if (!fromDate.equals(toDate)) + datesL.add(to); + Date[] dates = datesL.toArray(new Date[0]); + return dates; + } + + public static Date fromInstant(Instant instant) { + if (instant == null) return null; + return Date.from(instant); + } + + public static Date from(LocalDateTime localDateTime) { + return localDateTime != null ? Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()) : null; + } + + public static Date from(LocalDate localDate) { + return localDate != null ? Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()) : null; + } + + /** + * иногда встречается java.sql.Date + * у них toInstant() не поддерживается + */ + public static Instant fromDate(Date dateToConvert) { + try { + if (dateToConvert == null) return null; + return dateToConvert.toInstant(); + } catch (UnsupportedOperationException e) { +// log.warn("java.sql.Date"); + if (dateToConvert instanceof java.sql.Date) { + return ((java.sql.Date) dateToConvert).toLocalDate().atStartOfDay(ZoneId.systemDefault()).toInstant(); + } else { + throw e; + } + } + } + + public static Instant fromDate(LocalDate dateToConvert) { + return dateToConvert == null ? null : dateToConvert.atStartOfDay(ZoneId.systemDefault()).toInstant(); + } + + public static LocalDateTime toDateTime(Instant instant) { + if (instant == null) { + return null; + } else { + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + } + } + + public static Date toUtilDate(java.sql.Date sqlDate) { + return sqlDate != null ? new Date(sqlDate.getTime()) : null; + } + + + public static boolean isCurrentTradingDay(Date date) { + Date tradingDay = getDateOnStartOfDay(date); + Date currentDay = getDateOnStartOfDay(new Date()); + return currentDay.equals(tradingDay); + } + + /** + * Начало дня по текущей временной зоне + */ + public static Instant startOfDayNow() { + ZonedDateTime zdtStart = ZonedDateTime.now(); + zdtStart = zdtStart.truncatedTo(ChronoUnit.DAYS); + return zdtStart.toInstant(); + } + + /** + * Начало дня по текущей временной зоне. + * + * @param from + * @return + */ + public static Instant startOfDay(Instant from) { + ZonedDateTime zdtStart = from.atZone(ZoneId.systemDefault()); + zdtStart = zdtStart.truncatedTo(ChronoUnit.DAYS); + return zdtStart.toInstant(); + } + + public static ZonedDateTime toZoned(Instant instant) { + if (instant == null) return null; + return instant.atZone(zone); + } + + public static LocalDate toDate(Instant instant) { + ZonedDateTime zoned = toZoned(instant); + if (zoned == null) return null; + return zoned.toLocalDate(); + } + + /** + * Сегодня (от начала дня) или позднее + * + * @param instant дата, может ыть null + * @return + */ + public static boolean afterToday(Instant instant) { + LocalDate localDate = toDate(instant); + return afterToday(localDate); + } + + /** + * Сегодня (от начала дня) или позднее + * + * @param date дата, может ыть null + * @return + */ + public static boolean afterToday(Date date) { + LocalDate localDate = toDate(date); + return afterToday(localDate); + } + + /** + * Сегодня (от начала дня) или позднее + * + * @param localDate дата, может ыть null + * @return + */ + public static boolean afterToday(LocalDate localDate) { + if (localDate == null) return false; + return !localDate.isBefore(LocalDate.now()); + } + + public static boolean isToday(Instant instant) { + LocalDate localDate = toDate(instant); + return isToday(localDate); + } + + public static boolean isToday(Date date) { + LocalDate localDate = toDate(date); + return isToday(localDate); + } + + public static boolean isToday(LocalDate localDate) { + if (localDate == null) return false; + return localDate.equals(LocalDate.now()); + } + + /** + * Если instant без даты (1970 год), то берёт из него время и добавляет текущую дату. + * + * @param time может быть null + * @return time, или если не указана дата, то текущая дата + время из time + */ + public static Instant addDateTodayToTimeIfNeeded(Instant time) { + if (time == null) + return null; + LocalDateTime ldt = TimeUtil.toDateTime(time); + if (ldt.getYear() == 1970 && ldt.getMonth() == Month.JANUARY && ldt.getDayOfMonth() == 1) { + // time without date + ldt = LocalDateTime.of(LocalDate.now(), ldt.toLocalTime()); + return ldt.atZone(ZoneId.systemDefault()).toInstant(); + } + return time; + } + + public static String formatInstantToString(DateTimeFormatter formatter, Instant date) { + if (formatter == null || date == null) return ""; + return formatter.format(date); + } +}