From db9c38288c3d3b45c8953561b7f71f297e890a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A8=D0=B5=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=B4=D0=B0?= Date: Mon, 25 Mar 2024 00:04:46 +0300 Subject: [PATCH 01/40] add README-en.md in english in accordance with issue #265 --- README-en.md | 218 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 README-en.md diff --git a/README-en.md b/README-en.md new file mode 100644 index 00000000..ca25a006 --- /dev/null +++ b/README-en.md @@ -0,0 +1,218 @@ +[![java-version](https://img.shields.io/badge/java-21-brightgreen?style=flat-square)](https://openjdk.org/) +[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.0.7-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) +[![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) +[![github-closed-pull-requests](https://img.shields.io/github/issues-pr-closed/spacious-team/investbook?style=flat-square&color=brightgreen)](https://github.com/spacious-team/investbook/pulls?q=is%3Apr+is%3Aclosed) +[![github-workflow-status](https://img.shields.io/github/actions/workflow/status/spacious-team/investbook/publish-docker.yml?style=flat-square&branch=master)](https://github.com/spacious-team/investbook/actions/workflows/publish-docker.yml) +[![github-all-releases](https://img.shields.io/github/downloads/spacious-team/investbook/total?style=flat-square&logo=github&color=lightblue)](https://github.com/spacious-team/investbook/releases/latest) +[![docker-pulls](https://img.shields.io/docker/pulls/spaciousteam/investbook?style=flat-square&logo=docker&color=lightblue&logoColor=white)](https://hub.docker.com/r/spaciousteam/investbook) +[![telegram-channel](https://img.shields.io/endpoint?style=flat-square&color=2ca5e0&label=news&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Finvestbook_official)](https://t.me/investbook_official) +[![telegram-group](https://img.shields.io/endpoint?style=flat-square&color=2ca5e0&label=chat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Finvestbook_support)](https://t.me/investbook_support) +[![telegram-support](https://img.shields.io/badge/support-online-2ca5e0?style=flat-square&logo=telegram)](https://t.me/investbook_support_bot) + +Find out the real annual percentage return on investment and compare it to a bank deposit, find out the average +cost of buying stocks, bonds, derivatives, automate, analyze your portfolio. + + + +#### Contents +- [Purpose](#purpose) +- [Difference from similar products](#difference-from-similar-products) +- [Brokers](#brokers) +- [Install](#install) +- [Working with the application](#working-with-the-application) +- [Application update](#application-update) +- [Documentation](#documentation) +- [License](#license) +- [Why is the application code open source](#why-is-the-application-code-open-source) +- [How to help](#how-to-help) +- [Contacts](#contacts) + +### Purpose +If you keep records of transactions in Excel or have heard that it should be kept +([recommendation 1](https://zen.yandex.ru/media/openjournal/kak-vesti-uchet-sdelok-v-excel-5d52616ea98a2a00ad258284), +[2](https://vse-dengy.ru/pro-investitsii/dohodnost-investitsiy-xirr.html), +[3](https://www.banki.ru/forum/?PAGE_NAME=read&FID=21&TID=325769)), then this free application will help you do it. + +Accounting for transactions in an Excel table, in contrast to broker reports, shows the history of the portfolio: +the average purchase price, financial results of transactions, history of dividends and coupons, withholding taxes on +payments and withdrawals, history of cash flows, the final return on investments and speculation since the opening of +the account, future tax withholding and tax obligations are not a complete list of information that the application will +calculate for you. + +Some brokers provide a personal account and charts, but may not disclose all the information. If you have +several accounts with different brokers, information will be presented in different places, volumes and formats. Application +objectively, displays data in a single format for all brokers. + +![main-page](https://user-images.githubusercontent.com/11336712/128609729-08b5cb5e-9f58-452e-a661-a0258d7fb512.png) + +![sectors-pie-chart](https://user-images.githubusercontent.com/11336712/120564463-a5cc8980-c413-11eb-8326-46efcdc85c23.gif) + +All you need to do is download the latest broker reports or [enter manually](src/main/asciidoc/investbook-forms.adoc) +information. In this case, all information is saved on your computer, the data does not go to the cloud, and the +Internet is not required to work. + +For each account separately and summing up a single total for all accounts, the following information will be available: +- [review](src/main/asciidoc/portfolio-analysis.adoc) of asset growth calculated using the S&P 500 method, + compared to the S&P 500, investment history and cash balances; + ![portfolio-analysis](https://user-images.githubusercontent.com/11336712/102415874-fd17a280-4009-11eb-9bff-232975adf21b.png) + + + +- [portfolio](src/main/asciidoc/portfolio-status.adoc) of securities with information about the current position, + average price purchases and yield of securities (CHISTVNDOH/XIRR), taking into account hedging positions in the derivatives + market and the average purchase price of currency; + ![portfolio](https://user-images.githubusercontent.com/11336712/104820094-af2dce80-5843-11eb-8083-6521ea537334.png) +- share of a security in a [portfolio](src/main/asciidoc/portfolio-status.adoc); + ![current-proportion](https://user-images.githubusercontent.com/11336712/88717010-8cd6b600-d128-11ea-901f-2b3fcee96f07.png) +- [trader's portfolio](src/main/asciidoc/derivatives-market-total-profit.adoc) with information on profitability transactions on the derivatives market in the context + of a group of contracts (for example, for all futures and options Si, the same for BR, etc.); + ![derivatives-marker-total-profit](https://user-images.githubusercontent.com/11336712/119887746-30f1df00-bf3d-11eb-9c52-713093ae4d72.png) +- distribution of profit across groups of derivatives contracts in the [trader’s portfolio](src/main/asciidoc/derivatives-market-total-profit.adoc); + ![derivatives-profit-proportion](https://user-images.githubusercontent.com/11336712/120565530-fb099a80-c415-11eb-82bb-8288ed9b7806.png) +- details of dividend, coupon and depreciation [payments](src/main/asciidoc/portfolio-payment.adoc); + ![portfolio-payment](https://user-images.githubusercontent.com/11336712/88460806-93a2c600-cea7-11ea-8ac9-95406fd6cec8.png) +- details of dividend, coupon and depreciation [payments](src/main/asciidoc/foreign-portfolio-payment.adoc), + accrued on shares and bonds from the linked IIS account; + ![foreign-portfolio-payment](https://user-images.githubusercontent.com/11336712/87988115-7907d000-cae8-11ea-9ec7-d56a120aac89.png) +- profitability of transactions on the [stock](src/main/asciidoc/stock-market-profit.adoc) market (FIFO method); + ![stock-market](https://user-images.githubusercontent.com/11336712/78156498-8de02b00-7447-11ea-833c-cfc755bd7558.png) +- profitability of transactions on the [derivatives](src/main/asciidoc/derivatives-market-profit.adoc) market; + ![derivatives-market](https://user-images.githubusercontent.com/11336712/78156504-8f115800-7447-11ea-87e5-3cd4c34aab47.png) +- profitability of transactions on the [foreign exchange](src/main/asciidoc/foreign-market-profit.adoc) market; + ![foreign-market](https://user-images.githubusercontent.com/11336712/84881751-fa59e600-b096-11ea-8b83-19d1c1229d73.png) +- [input and output](src/main/asciidoc/securities-deposit-and-withdrawal.adoc) of securities from/to other accounts, conversion, split of shares (AAPL, TSLA, etc.); + +- [profitability](src/main/asciidoc/cash-flow.adoc) of portfolio (CHISTVNDOH/XIRR), replenishments, write-offs, transfers from/to other accounts, +- current cash balance; + ![cash-in](https://user-images.githubusercontent.com/11336712/100395491-3172f100-3052-11eb-9652-cd5730ac2e6f.png) +- [tax](src/main/asciidoc/tax.adoc) burden, including the + [obligation](src/main/asciidoc/stock-market-profit.adoc#tax-liability) to independently pay tax for foreign securities; + ![tax](https://user-images.githubusercontent.com/11336712/96353102-b83ac280-10d1-11eb-9024-b0de4f4b153e.png) +- [broker](src/main/asciidoc/commission.adoc) commission. + + +### Difference from similar products +Investbook in comparison with [Intelinvest](https://intelinvest.ru) and [Snowball Income](https://snowball-income.com) +has the following advantages: + +1. No monthly subscription. For free. Always and unconditionally. +2. Dividends, coupons and tax payments are taken into account upon receipt. This allows you to accurately calculate + profitability, timely track delays in receipt of payments to the account. +3. High accuracy of portfolio value assessment and tax liabilities. Unlike analogues of the amount and date of receipt + payments specified in the application can be directly used to draw up a 3-NDFL declaration. +4. No Internet access required, the ability to work in your free time while traveling without the Internet. +5. Safety. No need to upload broker reports to the cloud, no need to provide an access token + to the brokerage account to third parties and software with potential errors. For example, it is known + that tokens of a popular broker allow you to make transactions without your participation. +6. Open source is an additional guarantee of security and confidence in the availability of data only to you. +7. Clear, widely used report format - Excel tables with [detailed description](src/main/asciidoc/investbook-report.adoc) + of each column. +8. Unified data presentation format "[Portfolio Open Format](https://github.com/spacious-team/portfolio-open-format)" + if necessary, it will allow you to painlessly transfer the accumulated data to another investment accounting application. + +### Brokers +The application analyzes reports from brokers Tinkoff (xlsx), Sberbank (xlsx), VTB (xls), Promsvyazbank (xlsx, xml) +and Your Broker / Uralsib (zip with xls). If your account is opened with another broker, +write to [us](https://t.me/investbook_support). You can also use it already on your version of the application +[forms](src/main/asciidoc/investbook-forms.adoc) for entering information or +[download](src/main/asciidoc/investbook-input-format.adoc) data from Excel file. Your broker's support can also be +offered to third-party developers through extension functionality. Instructions for installing extensions are available for +operating systems [windows](docs/install-on-windows.md), [mac](docs/install-on-linux.md) and [linux](docs/install-on-linux.md). + +### Install +Download the `.msi` installer from the [project] page (https://github.com/spacious-team/investbook/releases/latest) +and run it. + +You can refer to more detailed instructions for installing and using the application for operating systems +[windows](docs/install-on-windows.md), [mac](docs/install-on-linux.md) and [linux](docs/install-on-linux.md). +Investbook can also be run in [docker](docs/run-by-docker.md). + +### Working with the application +Launch the application via a shortcut on the Windows desktop, in the browser go to http://localhost:2030 +and download broker reports (from your local computer or email). + +For convenience, the application allows: +1. Downloading the same report multiple times (useful if you don't remember whether you downloaded a particular + report or not), There will be no data duplication. +2. Downloading reports for any time interval (day, month, year, etc.), and it is acceptable that reports of different + time periods will overlap. +3. It is acceptable to download reports for multiple brokerage/investment accounts, including from different brokerage + houses. + +After downloading the report, analytical download in [excel file](src/main/asciidoc/investbook-report.adoc) +format becomes available. + +### Application update +The update process on Windows is no different from the initial installation process. Use the instructions +for operating systems [windows](docs/install-on-windows.md), [mac](docs/install-on-linux.md) or +[linux](docs/install-on-linux.md). Or, if Investbook was run in docker, use +[instructions](docs/run-by-docker.md). + +### Documentation +Additional information can be found in [documentation](docs/documentation.md), also offline documentation is always +available is available to you in the installed application on the main page via the "Documentation" link. + +### License +The application is free (you are allowed to use, distribute, copy and make changes). +The license text is available in [English](https://www.gnu.org/licenses/agpl-3.0.html) and +[Russian](http://antirao.ru/gpltrans/agplru.pdf) languages, and also available +[explanation](https://www.gnu.org/licenses/quick-guide-gplv3.html) +and answers to [questions](https://www.gnu.org/licenses/gpl-faq.ru.html) in Russian. + +The license implies that the application is released to the public. Application version +from [page](https://github.com/spacious-team/investbook/releases) will always be distributed free of charge. +But the license also allows any developer to improve their own copy of the application, including for the purpose of +[sales](https://www.gnu.org/licenses/gpl-faq.ru.html#DoesTheGPLAllowMoney) (with the caveat that the modified source code +will be open on the Internet). + +### Why is the application code open source +The idea of open source is the freedom to develop and use software. +Many famous brands use open source, [Instagram](https://github.com/Instagram) for example, +[Android](https://ru.wikipedia.org/wiki/Android#%D0%98%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BE%D0%B4), +[Telegram](https://ru.wikipedia.org/wiki/Telegram), [Twitter](https://opensource.twitter.dev/), +[Google Chrome](https://ru.wikipedia.org/wiki/Google_Chrome), +[Mozilla Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Source_Code/Downloading_Source_Archives), +sites with a secure [https](https://ru.wikipedia.org/wiki/OpenSSL) connection, such as https://vk.com, etc. +For some areas, open source solutions are better suited than others, for example in the fields of finance and data +encryption, because these solutions can be trusted because you or anyone else can look at the code and see +in the security of the program. + +
+Elon Musk's opinion on open source. + +> We will publish more source code and make it publicly available. And of course he will also be criticized, +people will help you find all the stupid things in the code. And we will quickly correct them, and we will do it under +full public control. I think this approach will allow us to gain public trust. Because here you don't have to take my +word for it, you can read the code with your own eyes, and what people say about this code. And you can see the +improvements we're making. You can monitor the entire process in real time and see all the improvements. I'd be surprised +If only society hadn't thought after that, "Wow, this looks like something we can trust!" Well, it's true, this story +should inspire much more trust than others with all their black boxes and refusal to show the inside story. +What are you trying to hide? Clearly not something good. If you have nothing to hide, why not show it? +> +> [_2023 year interview_](https://www.youtube.com/watch?v=bOznEZAjX3I&t=5138s) +
+ +### How to help +You can help by expanding or correcting the [documentation](https://github.com/spacious-team/investbook/files/5398264/github.docx), +[reporting](https://github.com/spacious-team/investbook/issues/new/choose) problems with the application, +[offering](https://github.com/spacious-team/investbook/issues/new/choose) new functionality or improving the Investbook application code. + +There is also [extension](/docs/extension-developer-guide.md) functionality, that allows third-party developers +expand the list of [brokers](#brokers) supported out of the box. Extensions can be connected at the request of users +to the application. Third-party developers can distribute extensions for free or for a [fee](https://youtu.be/q4O6PX0ZuFU), +therefore, developers, even pursuing different goals, work together. If you decide to improve the application in this +repository, please read the following [information](docs/CONTRIBUTING.md). + +### Contacts +- Telegram [channel](https://t.me/investbook_official), technical [support](https://t.me/investbook_support_bot) + and users [chat](https://t.me/investbook_support); +- Discussion on the forum [banki.ru](https://www.banki.ru/forum/?PAGE_NAME=read&FID=21&TID=380178); +- Application page on the [smart-lab.ru](https://smart-lab.ru/trading-software/Investbook) and + [contact page](https://smart-lab.ru/profile/SpaciousTeam); +- e-mail: [spacious-team@ya.ru](mailto:spacious-team@ya.ru). + +You can leave your review on the website [otzovik.com](https://otzovik.com/reviews/investbook-prilozhenie_investora_i_treydera/). + +Evaluate investment performance easily and confidentially. + + From df46944e9f020a57c083a5176d0939b5903d6854 Mon Sep 17 00:00:00 2001 From: vananiev Date: Wed, 27 Mar 2024 22:46:48 +0300 Subject: [PATCH 02/40] add lang icons to readme --- README-en.md | 3 +++ README.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/README-en.md b/README-en.md index ca25a006..f5a12d90 100644 --- a/README-en.md +++ b/README-en.md @@ -1,3 +1,6 @@ +[](README-en.md) +[](README.md)
+ [![java-version](https://img.shields.io/badge/java-21-brightgreen?style=flat-square)](https://openjdk.org/) [![spring-boot-version](https://img.shields.io/badge/spring--boot-3.0.7-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) diff --git a/README.md b/README.md index edc60cc9..5b548ad5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[](README-en.md) +[](README.md)
+ [![java-version](https://img.shields.io/badge/java-21-brightgreen?style=flat-square)](https://openjdk.org/) [![spring-boot-version](https://img.shields.io/badge/spring--boot-3.0.7-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) From a7680061e12d2e8b15b1ea8d194e28ee46545c76 Mon Sep 17 00:00:00 2001 From: vananiev Date: Wed, 27 Mar 2024 22:48:56 +0300 Subject: [PATCH 03/40] fix image style --- README-en.md | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README-en.md b/README-en.md index f5a12d90..d9290aa9 100644 --- a/README-en.md +++ b/README-en.md @@ -1,5 +1,5 @@ -[](README-en.md) -[](README.md)
+[](README-en.md) +[](README.md)
[![java-version](https://img.shields.io/badge/java-21-brightgreen?style=flat-square)](https://openjdk.org/) [![spring-boot-version](https://img.shields.io/badge/spring--boot-3.0.7-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) diff --git a/README.md b/README.md index 5b548ad5..de924170 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[](README-en.md) -[](README.md)
+[](README-en.md) +[](README.md)
[![java-version](https://img.shields.io/badge/java-21-brightgreen?style=flat-square)](https://openjdk.org/) [![spring-boot-version](https://img.shields.io/badge/spring--boot-3.0.7-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) From 25d3504ea643ccd0d039549e3d9cd7b340669e60 Mon Sep 17 00:00:00 2001 From: vananiev Date: Fri, 5 Apr 2024 19:49:11 +0300 Subject: [PATCH 04/40] version 2024.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 049be096..1d68fc0c 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ ru.investbook investbook - 2024.1 + 2024.2 investbook Investor Accounting Book @@ -62,7 +62,7 @@ - 24.1 + 24.2 21 From 9aeee76cab5d820e7f1361215309298605219187 Mon Sep 17 00:00:00 2001 From: vananiev Date: Fri, 5 Apr 2024 20:15:08 +0300 Subject: [PATCH 05/40] update to java 22 --- .github/workflows/publish-docker.yml | 2 +- README-en.md | 2 +- README.md | 2 +- docs/install-on-linux.md | 11 ++++++----- docs/install-on-windows-by-zip.md | 6 +++--- pom.xml | 3 ++- src/main/assembly/zip/start.bat | 2 +- src/main/assembly/zip/start.sh | 2 +- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 74389994..6dd0d9f6 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -18,7 +18,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v3 with: - java-version: '21' + java-version: '22' distribution: 'liberica' cache: maven - name: Publish diff --git a/README-en.md b/README-en.md index d9290aa9..8ab31236 100644 --- a/README-en.md +++ b/README-en.md @@ -1,7 +1,7 @@ [](README-en.md) [](README.md)
-[![java-version](https://img.shields.io/badge/java-21-brightgreen?style=flat-square)](https://openjdk.org/) +[![java-version](https://img.shields.io/badge/java-22-brightgreen?style=flat-square)](https://openjdk.org/) [![spring-boot-version](https://img.shields.io/badge/spring--boot-3.0.7-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) [![github-closed-pull-requests](https://img.shields.io/github/issues-pr-closed/spacious-team/investbook?style=flat-square&color=brightgreen)](https://github.com/spacious-team/investbook/pulls?q=is%3Apr+is%3Aclosed) diff --git a/README.md b/README.md index de924170..58f5a29f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [](README-en.md) [](README.md)
-[![java-version](https://img.shields.io/badge/java-21-brightgreen?style=flat-square)](https://openjdk.org/) +[![java-version](https://img.shields.io/badge/java-22-brightgreen?style=flat-square)](https://openjdk.org/) [![spring-boot-version](https://img.shields.io/badge/spring--boot-3.0.7-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) [![github-closed-pull-requests](https://img.shields.io/github/issues-pr-closed/spacious-team/investbook?style=flat-square&color=brightgreen)](https://github.com/spacious-team/investbook/pulls?q=is%3Apr+is%3Aclosed) diff --git a/docs/install-on-linux.md b/docs/install-on-linux.md index 25f049c0..de61b600 100644 --- a/docs/install-on-linux.md +++ b/docs/install-on-linux.md @@ -12,16 +12,16 @@ ```shell script $ java -version ``` - Если версия 21 или выше, пропустите следующий пункт. + Если версия 22 или выше, пропустите следующий пункт. 2. Воспользуйтесь менеджером пакетов вашей ОС для установки java, например на Ubuntu ```shell - $ sudo apt install openjdk-21-jre-headless + $ sudo apt install openjdk-22-jre-headless ``` - Или можно скачать [Java 21](https://jdk.java.net/21/) и распаковать папку `jdk-21` в директорию `/opt`. - Для 32 разрядных ОС можно скачать [Java 21 x86 JRE](https://bell-sw.com/pages/downloads/). Если java распакована из + Или можно скачать [Java 22](https://jdk.java.net/22/) и распаковать папку `jdk-22` в директорию `/opt`. + Для 32 разрядных ОС можно скачать [Java 22 x86 32](https://bell-sw.com/pages/downloads/). Если java распакована из архива, то в директории приложения нужно найти файл `start.sh`, раскомментировать и актуализировать `JAVA_HOME` ```shell - $ export JAVA_HOME=/opt/jdk-21 + $ export JAVA_HOME=/opt/jdk-22 ``` 3. Скачать со страницы [проекта](https://github.com/spacious-team/investbook/releases/latest) архив `.zip` и распаковать в директорию `/opt`. @@ -55,6 +55,7 @@ | с 2022.7 | java 18 и выше | | с 2023.1 | java 20 и выше | | с 2023.3 | java 21 и выше | +| с 2024.2 | java 22 и выше | #### Установка расширений diff --git a/docs/install-on-windows-by-zip.md b/docs/install-on-windows-by-zip.md index fa49349e..57712ae1 100644 --- a/docs/install-on-windows-by-zip.md +++ b/docs/install-on-windows-by-zip.md @@ -9,13 +9,13 @@ Данная инструкция поддерживает все версии Windows. Если у вас 64 битная Windows, рекомендуем устанавливать по [инструкции](install-on-windows.md). -1. Создать папку `C:\Program Files\Java\`, скачать Java 21 под вашу версию операционной системы. +1. Создать папку `C:\Program Files\Java\`, скачать Java 22 под вашу версию операционной системы. Если у вас 32 битная Windows можете использовать скачать по [ссылке](https://libericajdk.ru/pages/downloads/) - (достаточно JRE сборки), если 64 битная - рекомендуется скачивать сборку [OpenJdk](https://jdk.java.net/21/). + (достаточно JRE сборки), если 64 битная - рекомендуется скачивать сборку [OpenJdk](https://jdk.java.net/22/). 1. Скачать со страницы [проекта](https://github.com/vananiev/portfolio/releases/latest) архив `investbook.zip` и распаковать в любую удобную папку. В ней открыть файл `start.bat` и изменить строчку ``` - #set JAVA_HOME=C:\Program Files\Java\jdk-21 + #set JAVA_HOME=C:\Program Files\Java\jdk-22 ``` следующим образом: + Убрать первый символ `#`. diff --git a/pom.xml b/pom.xml index 1d68fc0c..7ff4145e 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 24.2 - 21 + 22 @@ -427,6 +427,7 @@ + gcr.io/paketo-buildpacks/java:10.2.1 diff --git a/src/main/assembly/zip/start.bat b/src/main/assembly/zip/start.bat index 270585d1..c4b62f5c 100644 --- a/src/main/assembly/zip/start.bat +++ b/src/main/assembly/zip/start.bat @@ -17,7 +17,7 @@ # # Задать путь к распакованному архиву с Java -#set JAVA_HOME=C:\Program Files\Java\jdk-21 +#set JAVA_HOME=C:\Program Files\Java\jdk-22 # Запуск приложения chcp 65001 diff --git a/src/main/assembly/zip/start.sh b/src/main/assembly/zip/start.sh index 7137f6d9..6a52a537 100644 --- a/src/main/assembly/zip/start.sh +++ b/src/main/assembly/zip/start.sh @@ -17,7 +17,7 @@ # # Задать путь к распакованному архиву с Java -#export JAVA_HOME=/opt/jdk-21 +#export JAVA_HOME=/opt/jdk-22 [ -n "$JAVA_HOME" ] && export PATH=$JAVA_HOME/bin:$PATH cd $(dirname $0) From e9344f2b542bb56a90284a491c7d79edadec6c90 Mon Sep 17 00:00:00 2001 From: vananiev Date: Mon, 8 Apr 2024 01:37:40 +0300 Subject: [PATCH 06/40] update to hibernate 6.4.4 --- pom.xml | 1 + .../api/AbstractRestController.java | 6 +- .../entity/AssignedOrIdentityGenerator.java | 63 +++++++++++++++++++ .../entity/EventCashFlowEntity.java | 4 +- .../ru/investbook/entity/IssuerEntity.java | 4 +- .../entity/PortfolioCashEntity.java | 4 +- .../entity/PortfolioPropertyEntity.java | 4 +- .../ru/investbook/entity/SecurityEntity.java | 4 +- .../entity/SecurityEventCashFlowEntity.java | 4 +- .../entity/SecurityQuoteEntity.java | 4 +- .../entity/TransactionCashFlowEntity.java | 4 +- .../investbook/entity/TransactionEntity.java | 4 +- .../UseExistingOrGenerateIdGenerator.java | 42 ------------- 13 files changed, 85 insertions(+), 63 deletions(-) create mode 100644 src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java delete mode 100644 src/main/java/ru/investbook/entity/UseExistingOrGenerateIdGenerator.java diff --git a/pom.xml b/pom.xml index 1d68fc0c..d7234c41 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ 24.2 21 + 6.4.4.Final diff --git a/src/main/java/ru/investbook/api/AbstractRestController.java b/src/main/java/ru/investbook/api/AbstractRestController.java index 02675c60..20a071fa 100644 --- a/src/main/java/ru/investbook/api/AbstractRestController.java +++ b/src/main/java/ru/investbook/api/AbstractRestController.java @@ -28,7 +28,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.util.UriUtils; import ru.investbook.converter.EntityConverter; -import ru.investbook.entity.UseExistingOrGenerateIdGenerator; import java.net.URI; import java.net.URISyntaxException; @@ -66,8 +65,9 @@ public ResponseEntity get(ID id) { * If entity has ID and record with this ID already exists in DB, CONFLICT http status and Location header was returned. * Otherwise, CREATE http status will be returned with Location header. * When creating new object, ID may be passed, but that ID only used when no {@link GeneratedValue} set - * on Entity ID field or if {@link GeneratedValue#generator} set to {@link UseExistingOrGenerateIdGenerator} - * or similar generator impl; otherwise ID, passed in object, will be ignored (see JPA impl). + * on Entity ID field or if {@link GeneratedValue#generator} set to {@link org.hibernate.id.Assigned}, + * {@link ru.investbook.entity.AssignedOrIdentityGenerator} or similar generator impl; + * otherwise ID, passed in object, will be ignored (see JPA impl). * * @param object new entity (ID may be missed) * @throws InternalServerErrorException if object not created or updated diff --git a/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java b/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java new file mode 100644 index 00000000..ce6c3281 --- /dev/null +++ b/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java @@ -0,0 +1,63 @@ +/* + * InvestBook + * Copyright (C) 2024 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package ru.investbook.entity; + +import jakarta.persistence.Id; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.EventType; +import org.hibernate.id.IdentityGenerator; + +import java.util.Objects; + +/** + * Behaves like {@link org.hibernate.id.Assigned} generator if entity {@link Id} is not null + * or like {@link org.hibernate.id.IdentityGenerator} otherwise. + */ +// IdentityGenerator can be replaced by SelectGenerator if DB doesn't support AUTO_INCREMENT/IDENTITY fields. +public class AssignedOrIdentityGenerator extends IdentityGenerator implements BeforeExecutionGenerator { + static final String NAME = "AssignedOrIdentityGenerator"; + + @Override + public Object generate(SharedSessionContractImplementor session, Object entity, Object currentValue, EventType eventType) { + return getId(entity, session); + } + + private static Object getId(Object entity, SharedSessionContractImplementor session) { + return session.getEntityPersister(null, entity) + .getIdentifier(entity, session); + } + + /** + * @implNote Method {@link BeforeExecutionGenerator#generate(SharedSessionContractImplementor, Object, Object, EventType)} + * is called if this method returns false + */ + @Override + public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) { + Object id = getId(entity, session); + return Objects.isNull(id); + } + + @Override + public boolean generatedOnExecution() { + // The choice must be made in the generatedOnExecution(entity, session) + // support mixed-timing generators (hibernate-core: + return true; + } +} diff --git a/src/main/java/ru/investbook/entity/EventCashFlowEntity.java b/src/main/java/ru/investbook/entity/EventCashFlowEntity.java index 16f8c371..94086f0e 100644 --- a/src/main/java/ru/investbook/entity/EventCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/EventCashFlowEntity.java @@ -42,8 +42,8 @@ @EqualsAndHashCode(of = "id") public class EventCashFlowEntity { @Id - @GeneratedValue(generator = UseExistingOrGenerateIdGenerator.NAME) - @GenericGenerator(name = UseExistingOrGenerateIdGenerator.NAME, strategy = UseExistingOrGenerateIdGenerator.STRATEGY) + @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) + @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/IssuerEntity.java b/src/main/java/ru/investbook/entity/IssuerEntity.java index 2aa06bb2..ac405bc0 100644 --- a/src/main/java/ru/investbook/entity/IssuerEntity.java +++ b/src/main/java/ru/investbook/entity/IssuerEntity.java @@ -31,8 +31,8 @@ @Data public class IssuerEntity { @Id - @GeneratedValue(generator = UseExistingOrGenerateIdGenerator.NAME) - @GenericGenerator(name = UseExistingOrGenerateIdGenerator.NAME, strategy = UseExistingOrGenerateIdGenerator.STRATEGY) + @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) + @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/PortfolioCashEntity.java b/src/main/java/ru/investbook/entity/PortfolioCashEntity.java index 07d26e4f..becb5e49 100644 --- a/src/main/java/ru/investbook/entity/PortfolioCashEntity.java +++ b/src/main/java/ru/investbook/entity/PortfolioCashEntity.java @@ -36,8 +36,8 @@ public class PortfolioCashEntity { @Id - @GeneratedValue(generator = UseExistingOrGenerateIdGenerator.NAME) - @GenericGenerator(name = UseExistingOrGenerateIdGenerator.NAME, strategy = UseExistingOrGenerateIdGenerator.STRATEGY) + @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) + @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java b/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java index 7dd38b5d..acf318ce 100644 --- a/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java +++ b/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java @@ -39,8 +39,8 @@ public class PortfolioPropertyEntity { @Id - @GeneratedValue(generator = UseExistingOrGenerateIdGenerator.NAME) - @GenericGenerator(name = UseExistingOrGenerateIdGenerator.NAME, strategy = UseExistingOrGenerateIdGenerator.STRATEGY) + @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) + @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/SecurityEntity.java b/src/main/java/ru/investbook/entity/SecurityEntity.java index 794b03c7..7529f958 100644 --- a/src/main/java/ru/investbook/entity/SecurityEntity.java +++ b/src/main/java/ru/investbook/entity/SecurityEntity.java @@ -41,8 +41,8 @@ public class SecurityEntity { public static final Pattern isinPattern = Pattern.compile("^[A-Z]{2}[A-Z0-9]{9}[0-9]$"); @Id - @GeneratedValue(generator = UseExistingOrGenerateIdGenerator.NAME) - @GenericGenerator(name = UseExistingOrGenerateIdGenerator.NAME, strategy = UseExistingOrGenerateIdGenerator.STRATEGY) + @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) + @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java b/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java index 5135fde2..942e0435 100644 --- a/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java @@ -42,8 +42,8 @@ @EqualsAndHashCode(of = "id") public class SecurityEventCashFlowEntity { @Id - @GeneratedValue(generator = UseExistingOrGenerateIdGenerator.NAME) - @GenericGenerator(name = UseExistingOrGenerateIdGenerator.NAME, strategy = UseExistingOrGenerateIdGenerator.STRATEGY) + @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) + @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java b/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java index 0057f807..3a43fcf4 100644 --- a/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java +++ b/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java @@ -40,8 +40,8 @@ public class SecurityQuoteEntity { @Id - @GeneratedValue(generator = UseExistingOrGenerateIdGenerator.NAME) - @GenericGenerator(name = UseExistingOrGenerateIdGenerator.NAME, strategy = UseExistingOrGenerateIdGenerator.STRATEGY) + @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) + @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java b/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java index feeaaa7c..88a4a737 100644 --- a/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java @@ -39,8 +39,8 @@ @EqualsAndHashCode(of = "id") public class TransactionCashFlowEntity { @Id - @GeneratedValue(generator = UseExistingOrGenerateIdGenerator.NAME) - @GenericGenerator(name = UseExistingOrGenerateIdGenerator.NAME, strategy = UseExistingOrGenerateIdGenerator.STRATEGY) + @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) + @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/TransactionEntity.java b/src/main/java/ru/investbook/entity/TransactionEntity.java index 5d930264..870c3d6d 100644 --- a/src/main/java/ru/investbook/entity/TransactionEntity.java +++ b/src/main/java/ru/investbook/entity/TransactionEntity.java @@ -43,8 +43,8 @@ public class TransactionEntity { @Id - @GeneratedValue(generator = UseExistingOrGenerateIdGenerator.NAME) - @GenericGenerator(name = UseExistingOrGenerateIdGenerator.NAME, strategy = UseExistingOrGenerateIdGenerator.STRATEGY) + @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) + @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/UseExistingOrGenerateIdGenerator.java b/src/main/java/ru/investbook/entity/UseExistingOrGenerateIdGenerator.java deleted file mode 100644 index c177a1bb..00000000 --- a/src/main/java/ru/investbook/entity/UseExistingOrGenerateIdGenerator.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * InvestBook - * Copyright (C) 2022 Spacious Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package ru.investbook.entity; - -import org.hibernate.HibernateException; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.id.IdentityGenerator; - -/** - * {@code @GeneratedValue} doesn't work out of box for H2. - * {@code @GeneratedValue(strategy = GenerationType.IDENTITY)} ignores entity id field set manually. - * - * @see Stack Overflow - */ -public class UseExistingOrGenerateIdGenerator extends IdentityGenerator { - static final String NAME = "UseExistingOrGenerateIdGenerator"; - static final String STRATEGY = "ru.investbook.entity.UseExistingOrGenerateIdGenerator"; - - @Override - public Object generate(SharedSessionContractImplementor session, Object object) throws HibernateException { - Object id = session.getEntityPersister(null, object) - .getClassMetadata() - .getIdentifier(object, session); - return (id == null) ? super.generate(session, object) : id; - } -} From 7538f772212ae34523a4e90a6fdaec8dd1dcec3c Mon Sep 17 00:00:00 2001 From: vananiev Date: Mon, 8 Apr 2024 02:02:27 +0300 Subject: [PATCH 07/40] add AssignedOrGeneratedValue annotation --- pom.xml | 2 +- .../entity/AssignedOrGeneratedValue.java | 58 +++++++++++++++++++ .../entity/AssignedOrIdentityGenerator.java | 13 +++-- .../entity/EventCashFlowEntity.java | 5 +- .../ru/investbook/entity/IssuerEntity.java | 5 +- .../entity/PortfolioCashEntity.java | 5 +- .../entity/PortfolioPropertyEntity.java | 5 +- .../ru/investbook/entity/SecurityEntity.java | 5 +- .../entity/SecurityEventCashFlowEntity.java | 5 +- .../entity/SecurityQuoteEntity.java | 5 +- .../entity/TransactionCashFlowEntity.java | 5 +- .../investbook/entity/TransactionEntity.java | 5 +- 12 files changed, 75 insertions(+), 43 deletions(-) create mode 100644 src/main/java/ru/investbook/entity/AssignedOrGeneratedValue.java diff --git a/pom.xml b/pom.xml index d7234c41..c594ea9b 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ 24.2 21 - 6.4.4.Final + 6.4.4.Final diff --git a/src/main/java/ru/investbook/entity/AssignedOrGeneratedValue.java b/src/main/java/ru/investbook/entity/AssignedOrGeneratedValue.java new file mode 100644 index 00000000..1a2819a4 --- /dev/null +++ b/src/main/java/ru/investbook/entity/AssignedOrGeneratedValue.java @@ -0,0 +1,58 @@ +/* + * InvestBook + * Copyright (C) 2024 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package ru.investbook.entity; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import org.hibernate.annotations.IdGeneratorType; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Provides ID generation strategy for the values of primary keys. + * The annotation may be applied to a primary key property with the {@link Id} annotation. + * Behaves like {@link GeneratedValue} if primary key is null or use assigned value otherwise. + *

+ * Annotation helps to replace code snippet + *

+ *     @Id
+ *     @GeneratedValue(generator = "generator-name")
+ *     @GenericGenerator(name = "generator-name", type = AssignedOrIdentityGenerator.class)
+ *     Integer id;
+ * 
+ * with shorter one + *
+ *     @Id
+ *     @AssignedOrGeneratedValue
+ *     Integer id;
+ * 
+ *

+ * + * @see jakarta.persistence.GeneratedValue + */ +@Retention(RUNTIME) +@Target({METHOD, FIELD}) +@IdGeneratorType(AssignedOrIdentityGenerator.class) +public @interface AssignedOrGeneratedValue { +} diff --git a/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java b/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java index ce6c3281..f866a307 100644 --- a/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java +++ b/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java @@ -24,15 +24,14 @@ import org.hibernate.generator.EventType; import org.hibernate.id.IdentityGenerator; -import java.util.Objects; +import static java.util.Objects.isNull; /** * Behaves like {@link org.hibernate.id.Assigned} generator if entity {@link Id} is not null * or like {@link org.hibernate.id.IdentityGenerator} otherwise. */ -// IdentityGenerator can be replaced by SelectGenerator if DB doesn't support AUTO_INCREMENT/IDENTITY fields. +// IdentityGenerator can be replaced by SelectGenerator if RDBMS doesn't support AUTO_INCREMENT/IDENTITY fields. public class AssignedOrIdentityGenerator extends IdentityGenerator implements BeforeExecutionGenerator { - static final String NAME = "AssignedOrIdentityGenerator"; @Override public Object generate(SharedSessionContractImplementor session, Object entity, Object currentValue, EventType eventType) { @@ -51,13 +50,15 @@ private static Object getId(Object entity, SharedSessionContractImplementor sess @Override public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) { Object id = getId(entity, session); - return Objects.isNull(id); + return isNull(id); } @Override public boolean generatedOnExecution() { - // The choice must be made in the generatedOnExecution(entity, session) - // support mixed-timing generators (hibernate-core: + // This method is called to configure a context (using this Generator) without knowledge of a specific Entity. + // The choice for the real Entity must be made in the this.generatedOnExecution(entity, session) method. + // For example, find out comment "support mixed-timing generators" in IdentifierGeneratorUtil.class (hibernate-core): + // true is required, if ID sometimes should be generated by RDBMS (for example by AUTO_INCREMENT) return true; } } diff --git a/src/main/java/ru/investbook/entity/EventCashFlowEntity.java b/src/main/java/ru/investbook/entity/EventCashFlowEntity.java index 94086f0e..d3b51737 100644 --- a/src/main/java/ru/investbook/entity/EventCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/EventCashFlowEntity.java @@ -23,14 +23,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.GenericGenerator; import java.math.BigDecimal; import java.time.Instant; @@ -42,8 +40,7 @@ @EqualsAndHashCode(of = "id") public class EventCashFlowEntity { @Id - @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) - @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) + @AssignedOrGeneratedValue @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/IssuerEntity.java b/src/main/java/ru/investbook/entity/IssuerEntity.java index ac405bc0..646cb621 100644 --- a/src/main/java/ru/investbook/entity/IssuerEntity.java +++ b/src/main/java/ru/investbook/entity/IssuerEntity.java @@ -20,19 +20,16 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Data; -import org.hibernate.annotations.GenericGenerator; @Entity @Table(name = "issuer") @Data public class IssuerEntity { @Id - @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) - @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) + @AssignedOrGeneratedValue @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/PortfolioCashEntity.java b/src/main/java/ru/investbook/entity/PortfolioCashEntity.java index becb5e49..981cf9fa 100644 --- a/src/main/java/ru/investbook/entity/PortfolioCashEntity.java +++ b/src/main/java/ru/investbook/entity/PortfolioCashEntity.java @@ -21,11 +21,9 @@ import jakarta.persistence.Basic; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Data; -import org.hibernate.annotations.GenericGenerator; import java.math.BigDecimal; import java.time.Instant; @@ -36,8 +34,7 @@ public class PortfolioCashEntity { @Id - @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) - @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) + @AssignedOrGeneratedValue @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java b/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java index acf318ce..6abf1609 100644 --- a/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java +++ b/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java @@ -23,13 +23,11 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; -import org.hibernate.annotations.GenericGenerator; import java.time.Instant; @@ -39,8 +37,7 @@ public class PortfolioPropertyEntity { @Id - @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) - @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) + @AssignedOrGeneratedValue @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/SecurityEntity.java b/src/main/java/ru/investbook/entity/SecurityEntity.java index 7529f958..e130459d 100644 --- a/src/main/java/ru/investbook/entity/SecurityEntity.java +++ b/src/main/java/ru/investbook/entity/SecurityEntity.java @@ -22,12 +22,10 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.GenericGenerator; import org.spacious_team.broker.pojo.SecurityType; import java.util.regex.Pattern; @@ -41,8 +39,7 @@ public class SecurityEntity { public static final Pattern isinPattern = Pattern.compile("^[A-Z]{2}[A-Z0-9]{9}[0-9]$"); @Id - @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) - @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) + @AssignedOrGeneratedValue @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java b/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java index 942e0435..c15df622 100644 --- a/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java @@ -23,14 +23,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.GenericGenerator; import java.math.BigDecimal; import java.time.Instant; @@ -42,8 +40,7 @@ @EqualsAndHashCode(of = "id") public class SecurityEventCashFlowEntity { @Id - @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) - @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) + @AssignedOrGeneratedValue @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java b/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java index 3a43fcf4..b3825738 100644 --- a/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java +++ b/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java @@ -23,13 +23,11 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; -import org.hibernate.annotations.GenericGenerator; import java.math.BigDecimal; import java.time.Instant; @@ -40,8 +38,7 @@ public class SecurityQuoteEntity { @Id - @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) - @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) + @AssignedOrGeneratedValue @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java b/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java index 88a4a737..1aa8e63d 100644 --- a/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java @@ -22,14 +22,12 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.GenericGenerator; import java.math.BigDecimal; @@ -39,8 +37,7 @@ @EqualsAndHashCode(of = "id") public class TransactionCashFlowEntity { @Id - @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) - @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) + @AssignedOrGeneratedValue @Column(name = "id") private Integer id; diff --git a/src/main/java/ru/investbook/entity/TransactionEntity.java b/src/main/java/ru/investbook/entity/TransactionEntity.java index 870c3d6d..f5ea4f83 100644 --- a/src/main/java/ru/investbook/entity/TransactionEntity.java +++ b/src/main/java/ru/investbook/entity/TransactionEntity.java @@ -23,7 +23,6 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -31,7 +30,6 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; -import org.hibernate.annotations.GenericGenerator; import java.time.Instant; @@ -43,8 +41,7 @@ public class TransactionEntity { @Id - @GeneratedValue(generator = AssignedOrIdentityGenerator.NAME) - @GenericGenerator(name = AssignedOrIdentityGenerator.NAME, type = AssignedOrIdentityGenerator.class) + @AssignedOrGeneratedValue @Column(name = "id") private Integer id; From 8ea83a04a5ccafbe554ed0c1b7c00192c9123dab Mon Sep 17 00:00:00 2001 From: vananiev Date: Mon, 8 Apr 2024 03:56:12 +0300 Subject: [PATCH 08/40] impl BeforeOrOnExecutionGenerator --- .../api/AbstractRestController.java | 5 +- .../entity/AssignedOrGeneratedValue.java | 9 +- .../entity/AssignedOrIdentityGenerator.java | 50 +++----- .../entity/BeforeOrOnExecutionGenerator.java | 112 ++++++++++++++++++ 4 files changed, 133 insertions(+), 43 deletions(-) create mode 100644 src/main/java/ru/investbook/entity/BeforeOrOnExecutionGenerator.java diff --git a/src/main/java/ru/investbook/api/AbstractRestController.java b/src/main/java/ru/investbook/api/AbstractRestController.java index 20a071fa..e43c0b59 100644 --- a/src/main/java/ru/investbook/api/AbstractRestController.java +++ b/src/main/java/ru/investbook/api/AbstractRestController.java @@ -65,9 +65,8 @@ public ResponseEntity get(ID id) { * If entity has ID and record with this ID already exists in DB, CONFLICT http status and Location header was returned. * Otherwise, CREATE http status will be returned with Location header. * When creating new object, ID may be passed, but that ID only used when no {@link GeneratedValue} set - * on Entity ID field or if {@link GeneratedValue#generator} set to {@link org.hibernate.id.Assigned}, - * {@link ru.investbook.entity.AssignedOrIdentityGenerator} or similar generator impl; - * otherwise ID, passed in object, will be ignored (see JPA impl). + * on Entity ID field or if {@link GeneratedValue#generator} set to {@link org.hibernate.generator.BeforeExecutionGenerator}, + * generator impl; otherwise ID, passed in object, will be ignored (see JPA impl). * * @param object new entity (ID may be missed) * @throws InternalServerErrorException if object not created or updated diff --git a/src/main/java/ru/investbook/entity/AssignedOrGeneratedValue.java b/src/main/java/ru/investbook/entity/AssignedOrGeneratedValue.java index 1a2819a4..a5c0471a 100644 --- a/src/main/java/ru/investbook/entity/AssignedOrGeneratedValue.java +++ b/src/main/java/ru/investbook/entity/AssignedOrGeneratedValue.java @@ -18,7 +18,6 @@ package ru.investbook.entity; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import org.hibernate.annotations.IdGeneratorType; @@ -32,13 +31,14 @@ /** * Provides ID generation strategy for the values of primary keys. * The annotation may be applied to a primary key property with the {@link Id} annotation. - * Behaves like {@link GeneratedValue} if primary key is null or use assigned value otherwise. + * Annotation behaves like {@code @GeneratedValue(strategy = IDENTITY)} if primary key is null. + * If primary key value is not null it will be inserted to DB. *

* Annotation helps to replace code snippet *

  *     @Id
- *     @GeneratedValue(generator = "generator-name")
- *     @GenericGenerator(name = "generator-name", type = AssignedOrIdentityGenerator.class)
+ *     @GeneratedValue(generator = "use-assigned-if-present")
+ *     @GenericGenerator(name = "use-assigned-if-present", type = AssignedOrIdentityGenerator.class)
  *     Integer id;
  * 
* with shorter one @@ -50,6 +50,7 @@ *

* * @see jakarta.persistence.GeneratedValue + * @see AssignedOrIdentityGenerator */ @Retention(RUNTIME) @Target({METHOD, FIELD}) diff --git a/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java b/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java index f866a307..b0f8b442 100644 --- a/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java +++ b/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java @@ -19,46 +19,24 @@ package ru.investbook.entity; import jakarta.persistence.Id; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.generator.BeforeExecutionGenerator; -import org.hibernate.generator.EventType; +import org.hibernate.id.Assigned; import org.hibernate.id.IdentityGenerator; -import static java.util.Objects.isNull; - /** - * Behaves like {@link org.hibernate.id.Assigned} generator if entity {@link Id} is not null - * or like {@link org.hibernate.id.IdentityGenerator} otherwise. + * If Entity ID is not null, then this ID is stored to DB. + * If Entity ID is null, then RDBM should generate ID itself. + *

+ * In other words: this generator behaves like {@link org.hibernate.id.Assigned} generator if entity's field, + * marked by {@link Id} annotation, is not null, or like {@link org.hibernate.id.IdentityGenerator} if this field is null. + *

+ * Applicable only for INSERT operations. */ -// IdentityGenerator can be replaced by SelectGenerator if RDBMS doesn't support AUTO_INCREMENT/IDENTITY fields. -public class AssignedOrIdentityGenerator extends IdentityGenerator implements BeforeExecutionGenerator { - - @Override - public Object generate(SharedSessionContractImplementor session, Object entity, Object currentValue, EventType eventType) { - return getId(entity, session); - } - - private static Object getId(Object entity, SharedSessionContractImplementor session) { - return session.getEntityPersister(null, entity) - .getIdentifier(entity, session); - } - - /** - * @implNote Method {@link BeforeExecutionGenerator#generate(SharedSessionContractImplementor, Object, Object, EventType)} - * is called if this method returns false - */ - @Override - public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) { - Object id = getId(entity, session); - return isNull(id); - } +class AssignedOrIdentityGenerator extends BeforeOrOnExecutionGenerator { - @Override - public boolean generatedOnExecution() { - // This method is called to configure a context (using this Generator) without knowledge of a specific Entity. - // The choice for the real Entity must be made in the this.generatedOnExecution(entity, session) method. - // For example, find out comment "support mixed-timing generators" in IdentifierGeneratorUtil.class (hibernate-core): - // true is required, if ID sometimes should be generated by RDBMS (for example by AUTO_INCREMENT) - return true; + @SuppressWarnings("unused") + public AssignedOrIdentityGenerator() { + // IdentityGenerator can be replaced by SelectGenerator + // if RDBMS doesn't support AUTO_INCREMENT/IDENTITY fields + super(new Assigned(), new IdentityGenerator()); } } diff --git a/src/main/java/ru/investbook/entity/BeforeOrOnExecutionGenerator.java b/src/main/java/ru/investbook/entity/BeforeOrOnExecutionGenerator.java new file mode 100644 index 00000000..fd2d1545 --- /dev/null +++ b/src/main/java/ru/investbook/entity/BeforeOrOnExecutionGenerator.java @@ -0,0 +1,112 @@ +/* + * InvestBook + * Copyright (C) 2024 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package ru.investbook.entity; + +import lombok.Getter; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.EventType; +import org.hibernate.generator.OnExecutionGenerator; +import org.hibernate.id.PostInsertIdentityPersister; +import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; +import org.hibernate.persister.entity.EntityPersister; + +import java.util.EnumSet; + +import static java.util.Objects.isNull; + + +/** + * Allows to determine at runtime (based on Entity for example) whether an ID should be generated on client or on DB server side. + * To implement this constructor accepts {@link BeforeExecutionGenerator} and {@link OnExecutionGenerator} ID generators. + *

+ * If java {@link BeforeExecutionGenerator#generate(SharedSessionContractImplementor, Object entity, Object, EventType)} + * returns not null ID, when this ID will be stored to DB. In another case, the DBMS must generate ID itself + * based on the strategy implemented by {@link OnExecutionGenerator}. + */ +class BeforeOrOnExecutionGenerator implements BeforeExecutionGenerator, OnExecutionGenerator { + + private final BeforeExecutionGenerator beforeExecutionGenerator; + private final OnExecutionGenerator onExecutionGenerator; + @Getter + private final EnumSet eventTypes; + + public BeforeOrOnExecutionGenerator(BeforeExecutionGenerator beforeExecutionGenerator, + OnExecutionGenerator onExecutionGenerator) { + this.beforeExecutionGenerator = beforeExecutionGenerator; + this.onExecutionGenerator = onExecutionGenerator; + this.eventTypes = EnumSet.copyOf(beforeExecutionGenerator.getEventTypes()); + eventTypes.addAll(onExecutionGenerator.getEventTypes()); + } + + @Override + public boolean generatedOnExecution() { + // This method is called to configure a context (using this Generator) without knowledge of a specific Entity. + // The choice for the real Entity must be made in the this.generatedOnExecution(entity, session) method. + // For example, find out comment "support mixed-timing generators" in IdentifierGeneratorUtil.class (hibernate-core): + // true is required, if this Generator want sometimes generate ID by RDBMS (for example by AUTO_INCREMENT) + return true; + } + + /** + * @implNote If this method returns false, + * then {@link #generate(SharedSessionContractImplementor, Object, Object, EventType)} is called to get ID + */ + @Override + public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) { + try { + EventType eventType = beforeExecutionGenerator.getEventTypes().iterator().next(); + Object id = beforeExecutionGenerator.generate(session, entity, null, eventType); + return isNull(id); + } catch (Exception e) { + return true; // RDBMS should generate ID + } + } + + @Override + public Object generate(SharedSessionContractImplementor session, Object entity, Object currentValue, EventType eventType) { + return beforeExecutionGenerator.generate(session, entity, currentValue, eventType); + } + + @Override + public boolean referenceColumnsInSql(Dialect dialect) { + return onExecutionGenerator.referenceColumnsInSql(dialect); + } + + @Override + public boolean writePropertyValue() { + return onExecutionGenerator.writePropertyValue(); + } + + @Override + public String[] getReferencedColumnValues(Dialect dialect) { + return onExecutionGenerator.getReferencedColumnValues(dialect); + } + + @Override + public InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(PostInsertIdentityPersister persister) { + return onExecutionGenerator.getGeneratedIdentifierDelegate(persister); + } + + @Override + public String[] getUniqueKeyPropertyNames(EntityPersister persister) { + return onExecutionGenerator.getUniqueKeyPropertyNames(persister); + } +} From d244a8da5241fc2bca4fd4d361c649ca6fb463b5 Mon Sep 17 00:00:00 2001 From: vananiev Date: Sun, 21 Apr 2024 20:32:16 +0300 Subject: [PATCH 09/40] refactor some classes --- pom.xml | 2 +- .../parser/BrokerReportParserServiceImpl.java | 31 +++++++++---------- .../parser/MailboxReportParserService.java | 5 ++- .../PsbBrokerForeignMarketReport.java | 4 +-- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pom.xml b/pom.xml index c594ea9b..e0c35c0c 100644 --- a/pom.xml +++ b/pom.xml @@ -210,7 +210,7 @@ org.projectlombok lombok - 1.18.30 + 1.18.32 true diff --git a/src/main/java/ru/investbook/parser/BrokerReportParserServiceImpl.java b/src/main/java/ru/investbook/parser/BrokerReportParserServiceImpl.java index 883da8ec..93af7c0a 100644 --- a/src/main/java/ru/investbook/parser/BrokerReportParserServiceImpl.java +++ b/src/main/java/ru/investbook/parser/BrokerReportParserServiceImpl.java @@ -31,7 +31,6 @@ import ru.investbook.InvestbookProperties; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -58,19 +57,18 @@ public class BrokerReportParserServiceImpl implements BrokerReportParserService @SneakyThrows @Override public void parseReport(InputStream inputStream, String fileName, String broker) { - try (ByteArrayInputStream is = castToByteArrayInputStream(inputStream)) { - long t0 = System.nanoTime(); - is.mark(Integer.MAX_VALUE); - String brokerName = parseReport0(is, fileName, broker); - if (investbookProperties.isReportBackup()) { - is.reset(); - Path path = saveToBackup(is, fileName, brokerName); - log.info("Загрузка отчета {} завершена за {}, бекап отчета сохранен в {}", - fileName, Duration.ofNanos(System.nanoTime() - t0), path.toAbsolutePath()); - } else { - log.info("Загрузка отчета {} завершена за {}, бекап отключен конфигурацией", - fileName, Duration.ofNanos(System.nanoTime() - t0)); - } + ByteArrayInputStream is = castToByteArrayInputStream(inputStream); + long t0 = System.nanoTime(); + is.mark(Integer.MAX_VALUE); + String brokerName = parseReport0(is, fileName, broker); + if (investbookProperties.isReportBackup()) { + is.reset(); + Path path = saveToBackup(is, fileName, brokerName); + log.info("Загрузка отчета {} завершена за {}, бекап отчета сохранен в {}", + fileName, Duration.ofNanos(System.nanoTime() - t0), path.toAbsolutePath()); + } else { + log.info("Загрузка отчета {} завершена за {}, бекап отключен конфигурацией", + fileName, Duration.ofNanos(System.nanoTime() - t0)); } } @@ -78,9 +76,8 @@ public static ByteArrayInputStream castToByteArrayInputStream(InputStream inputS if (inputStream instanceof ByteArrayInputStream) { return (ByteArrayInputStream) inputStream; } else { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - inputStream.transferTo(out); - return new ByteArrayInputStream(out.toByteArray()); + byte[] bytes = inputStream.readAllBytes(); + return new ByteArrayInputStream(bytes); } } diff --git a/src/main/java/ru/investbook/parser/MailboxReportParserService.java b/src/main/java/ru/investbook/parser/MailboxReportParserService.java index 65c20d4a..c282cc9c 100644 --- a/src/main/java/ru/investbook/parser/MailboxReportParserService.java +++ b/src/main/java/ru/investbook/parser/MailboxReportParserService.java @@ -38,6 +38,7 @@ import org.springframework.stereotype.Component; import ru.investbook.web.model.MailboxDescriptor; +import java.io.InputStream; import java.time.Duration; import java.util.Properties; import java.util.stream.Stream; @@ -140,7 +141,9 @@ private boolean handleBodyPart(BodyPart bodyPart, MailboxDescriptor mailbox) { if (bodyPart.getFileName() != null) { DataHandler dataHandler = bodyPart.getDataHandler(); DataSource dataSource = dataHandler.getDataSource(); - brokerReportParserService.parseReport(dataSource.getInputStream(), dataSource.getName(), mailbox.getBroker()); + try (InputStream inputStream = dataSource.getInputStream()) { + brokerReportParserService.parseReport(inputStream, dataSource.getName(), mailbox.getBroker()); + } return true; } } catch (Exception e) { diff --git a/src/main/java/ru/investbook/parser/psb/foreignmarket/PsbBrokerForeignMarketReport.java b/src/main/java/ru/investbook/parser/psb/foreignmarket/PsbBrokerForeignMarketReport.java index 60930588..c9de2950 100644 --- a/src/main/java/ru/investbook/parser/psb/foreignmarket/PsbBrokerForeignMarketReport.java +++ b/src/main/java/ru/investbook/parser/psb/foreignmarket/PsbBrokerForeignMarketReport.java @@ -60,14 +60,14 @@ public PsbBrokerForeignMarketReport(String excelFileName, InputStream is, Securi private static Workbook getWorkbook(InputStream is) { try { ExcelReader reader = new ExcelReader(); - is = skeepNewLines(is); // required by ExcelReader + is = skipNewLines(is); // required by ExcelReader return reader.getWorkbook(new InputSource(is)); } catch (Exception e) { throw new RuntimeException("Не смог открыть xml файл", e); } } - private static InputStream skeepNewLines(InputStream is) throws IOException { + private static InputStream skipNewLines(InputStream is) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream(is.readAllBytes()); int symbol; do { From a93b780f45acb6d4c0322411f1c024f5c510683f Mon Sep 17 00:00:00 2001 From: vananiev Date: Tue, 23 Apr 2024 00:03:10 +0300 Subject: [PATCH 10/40] update spring boot to 3.2.5 --- pom.xml | 3 +-- .../java/ru/investbook/entity/AssignedOrIdentityGenerator.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index e0c35c0c..69ecc818 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.springframework.boot spring-boot-starter-parent - 3.0.7 + 3.2.5 ru.investbook @@ -64,7 +64,6 @@ 24.2 21 - 6.4.4.Final diff --git a/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java b/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java index b0f8b442..b3b14b45 100644 --- a/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java +++ b/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java @@ -31,7 +31,7 @@ *

* Applicable only for INSERT operations. */ -class AssignedOrIdentityGenerator extends BeforeOrOnExecutionGenerator { +public class AssignedOrIdentityGenerator extends BeforeOrOnExecutionGenerator { @SuppressWarnings("unused") public AssignedOrIdentityGenerator() { From 6dd5399eef6b311d393e7d383747a78183beb722 Mon Sep 17 00:00:00 2001 From: vananiev Date: Tue, 23 Apr 2024 00:08:18 +0300 Subject: [PATCH 11/40] fix spring boot update (#593) --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 69ecc818..83264eb2 100644 --- a/pom.xml +++ b/pom.xml @@ -171,6 +171,7 @@ com.h2database h2 + 2.1.214 runtime From 574a1ee093031836827f5a61d55d0bf0762294dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 21:08:47 +0000 Subject: [PATCH 12/40] Bump com.h2database:h2 from 2.1.214 to 2.2.220 Bumps [com.h2database:h2](https://github.com/h2database/h2database) from 2.1.214 to 2.2.220. - [Release notes](https://github.com/h2database/h2database/releases) - [Commits](https://github.com/h2database/h2database/compare/version-2.1.214...version-2.2.220) --- updated-dependencies: - dependency-name: com.h2database:h2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 83264eb2..1d5ded13 100644 --- a/pom.xml +++ b/pom.xml @@ -171,7 +171,7 @@ com.h2database h2 - 2.1.214 + 2.2.220 runtime From 212a192db254f0e602d50d7b60ce76baa5c1902b Mon Sep 17 00:00:00 2001 From: vananiev Date: Sat, 4 May 2024 16:32:27 +0300 Subject: [PATCH 13/40] update h2 to 2.2.224 and import db --- pom.xml | 1 - src/main/java/ru/investbook/InvestbookProperties.java | 4 +++- src/main/resources/application-dev.properties | 2 +- src/main/resources/application-h2.properties | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 1d5ded13..69ecc818 100644 --- a/pom.xml +++ b/pom.xml @@ -171,7 +171,6 @@ com.h2database h2 - 2.2.220 runtime diff --git a/src/main/java/ru/investbook/InvestbookProperties.java b/src/main/java/ru/investbook/InvestbookProperties.java index cc6ddab3..d5a7ccf4 100644 --- a/src/main/java/ru/investbook/InvestbookProperties.java +++ b/src/main/java/ru/investbook/InvestbookProperties.java @@ -38,7 +38,9 @@ public class InvestbookProperties { private Path reportBackupPath = dataPath.resolve("report-backups"); - private List sqlImportFiles = List.of(dataPath.resolve("export-2022.9.sql")); + private List sqlImportFiles = List.of( + dataPath.resolve("export-2022.9.sql"), + dataPath.resolve("export-2024.1.x.sql")); private boolean openHomePageAfterStart = false; diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 8d2dd927..f901f26e 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -24,7 +24,7 @@ #spring.datasource.driver-class-name=org.mariadb.jdbc.Driver # H2 -spring.datasource.url=jdbc:h2:file:~/investbook2-test;mode=mysql;non_keywords=value +spring.datasource.url=jdbc:h2:file:~/investbook3-test;mode=mysql;non_keywords=value spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.datasource.username = sa spring.datasource.password = diff --git a/src/main/resources/application-h2.properties b/src/main/resources/application-h2.properties index b1dce744..b0454f1f 100644 --- a/src/main/resources/application-h2.properties +++ b/src/main/resources/application-h2.properties @@ -16,7 +16,7 @@ # along with this program. If not, see . # -spring.datasource.url=jdbc:h2:file:~/investbook/investbook2;mode=mysql;non_keywords=value +spring.datasource.url=jdbc:h2:file:~/investbook/investbook3;mode=mysql;non_keywords=value spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.datasource.username = sa spring.datasource.password = From 2079f1681cd89808a80efaa613078f97005337ec Mon Sep 17 00:00:00 2001 From: vananiev Date: Sun, 19 May 2024 22:45:07 +0300 Subject: [PATCH 14/40] update spring boot version in readme --- README-en.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README-en.md b/README-en.md index d9290aa9..0627e3ef 100644 --- a/README-en.md +++ b/README-en.md @@ -2,7 +2,7 @@ [](README.md)
[![java-version](https://img.shields.io/badge/java-21-brightgreen?style=flat-square)](https://openjdk.org/) -[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.0.7-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) +[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.2.5-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) [![github-closed-pull-requests](https://img.shields.io/github/issues-pr-closed/spacious-team/investbook?style=flat-square&color=brightgreen)](https://github.com/spacious-team/investbook/pulls?q=is%3Apr+is%3Aclosed) [![github-workflow-status](https://img.shields.io/github/actions/workflow/status/spacious-team/investbook/publish-docker.yml?style=flat-square&branch=master)](https://github.com/spacious-team/investbook/actions/workflows/publish-docker.yml) diff --git a/README.md b/README.md index de924170..e9cb80a7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [](README.md)
[![java-version](https://img.shields.io/badge/java-21-brightgreen?style=flat-square)](https://openjdk.org/) -[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.0.7-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) +[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.2.5-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) [![github-closed-pull-requests](https://img.shields.io/github/issues-pr-closed/spacious-team/investbook?style=flat-square&color=brightgreen)](https://github.com/spacious-team/investbook/pulls?q=is%3Apr+is%3Aclosed) [![github-workflow-status](https://img.shields.io/github/actions/workflow/status/spacious-team/investbook/publish-docker.yml?style=flat-square&branch=master)](https://github.com/spacious-team/investbook/actions/workflows/publish-docker.yml) From 125fa4b5d69000a9a80f4f27fe860c7c31224dc3 Mon Sep 17 00:00:00 2001 From: vananiev Date: Sun, 19 May 2024 22:51:18 +0300 Subject: [PATCH 15/40] update paketo-buildpacks/java --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7ff4145e..de37b237 100644 --- a/pom.xml +++ b/pom.xml @@ -427,8 +427,7 @@ - - gcr.io/paketo-buildpacks/java:10.2.1 + gcr.io/paketo-buildpacks/java:14.0.0 From 2c30ad1ac4e0a07525c5ab58b49233e7c823d3f4 Mon Sep 17 00:00:00 2001 From: vananiev Date: Mon, 20 May 2024 00:53:15 +0300 Subject: [PATCH 16/40] update readme --- README-en.md | 9 ++++----- README.md | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/README-en.md b/README-en.md index badb9234..86373d45 100644 --- a/README-en.md +++ b/README-en.md @@ -169,16 +169,15 @@ But the license also allows any developer to improve their own copy of the appli will be open on the Internet). ### Why is the application code open source -The idea of open source is the freedom to develop and use software. -Many famous brands use open source, [Instagram](https://github.com/Instagram) for example, +For some areas, open source solutions are better suited than others, for example in the fields of finance and data +encryption, because these solutions can be trusted because you or anyone else can look at the code and see +in the security of the program. Many famous brands use open source, [Instagram](https://github.com/Instagram) for example, [Android](https://ru.wikipedia.org/wiki/Android#%D0%98%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BE%D0%B4), [Telegram](https://ru.wikipedia.org/wiki/Telegram), [Twitter](https://opensource.twitter.dev/), [Google Chrome](https://ru.wikipedia.org/wiki/Google_Chrome), [Mozilla Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Source_Code/Downloading_Source_Archives), sites with a secure [https](https://ru.wikipedia.org/wiki/OpenSSL) connection, such as https://vk.com, etc. -For some areas, open source solutions are better suited than others, for example in the fields of finance and data -encryption, because these solutions can be trusted because you or anyone else can look at the code and see -in the security of the program. +The idea of open source is the freedom to develop and use software.

Elon Musk's opinion on open source. diff --git a/README.md b/README.md index e06431b6..915948f9 100644 --- a/README.md +++ b/README.md @@ -159,16 +159,15 @@ Investbook также может быть запущен в [docker](docs/run-by будет открыт в сети интернет). ### Почему код приложения открыт -Идея открытого исходного кода (open source) заключается в свободе разработки и использования программного обеспечения. -Многие известные бренды используют open source, например [Instagram](https://github.com/Instagram), +Для некоторых сфер решения с открытым исходным кодом подходят лучше других, например в сферах финансов и шифрования данных, +т.к. этим решениям можно доверять вследствие того, что вы или любой другой желающий может посмотреть код и убедиться +в безопасности программы. Многие известные бренды используют open source, например [Instagram](https://github.com/Instagram), [Android](https://ru.wikipedia.org/wiki/Android#%D0%98%D1%81%D1%85%D0%BE%D0%B4%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BE%D0%B4), [Telegram](https://ru.wikipedia.org/wiki/Telegram), [Twitter](https://opensource.twitter.dev/), [Google Chrome](https://ru.wikipedia.org/wiki/Google_Chrome), [Mozilla Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Source_Code/Downloading_Source_Archives), сайты с защищенным соединением [https](https://ru.wikipedia.org/wiki/OpenSSL), такие как https://vk.com и др. -Для некоторых сфер решения с открытым исходным кодом подходят лучше других, например в сферах финансов и шифрования данных, -т.к. этим решениям можно доверять вследствие того, что вы или любой другой желающий может посмотреть код и убедиться -в безопасности программы. +Идея открытого исходного кода (open source) заключается в свободе разработки и использования программного обеспечения.
Мнение Илона Маска об открытом исходном коде. From 419c197821e1fd4e7698cf408da6e95d0aa8142f Mon Sep 17 00:00:00 2001 From: vananiev Date: Tue, 21 May 2024 22:43:00 +0300 Subject: [PATCH 17/40] refactor AbstractRestController --- .../api/AbstractRestController.java | 30 ++++++++++++------- .../api/SecurityRestController.java | 6 ++-- ...AbstractCbrForeignExchangeRateService.java | 2 +- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/main/java/ru/investbook/api/AbstractRestController.java b/src/main/java/ru/investbook/api/AbstractRestController.java index e43c0b59..ed7f0724 100644 --- a/src/main/java/ru/investbook/api/AbstractRestController.java +++ b/src/main/java/ru/investbook/api/AbstractRestController.java @@ -76,16 +76,18 @@ protected ResponseEntity post(Pojo object) { try { ID id = getId(object); if (id == null) { - return createEntity(object); + return createOrUpdateEntity(object); } - Optional result = getById(id); - if (result.isPresent()) { + if (existsById(id)) { return ResponseEntity .status(HttpStatus.CONFLICT) .location(getLocationURI(object)) .build(); } else { - return createEntity(object); + // Если убрать @Transactional над методом, то следующая строка может обновить объект по ошибке. + // Это возможно, если объект был создан другим потоком после проверки существования строки по ID. + // По этой причине @Transactional убирать не нужно. + return createOrUpdateEntity(object); } } catch (Exception e) { throw new InternalServerErrorException("Не могу создать объект", e); @@ -114,31 +116,37 @@ public ResponseEntity put(ID id, Pojo object) { throw new BadRequestException("Идентификатор объекта, переданный в URI [" + id + "] и в теле " + "запроса [" + getId(object) + "] не совпадают"); } - Optional result = getById(id); - if (result.isPresent()) { + if (existsById(id)) { saveAndFlush(object); return ResponseEntity.ok().build(); } else { - return createEntity(object); + return createOrUpdateEntity(object); } } catch (Exception e) { throw new InternalServerErrorException("Не могу создать объект", e); } } + private boolean existsById(ID id) { + return repository.existsById(id); + } + protected abstract Pojo updateId(ID id, Pojo object); private Entity saveAndFlush(Pojo object) { - return repository.saveAndFlush(converter.toEntity(object)); + Entity entity = converter.toEntity(object); + return repository.saveAndFlush(entity); } /** * @return response entity with http CREATE status, Location http header and body */ - private ResponseEntity createEntity(Pojo object) throws URISyntaxException { + private ResponseEntity createOrUpdateEntity(Pojo object) throws URISyntaxException { Entity entity = saveAndFlush(object); + Pojo savedObject = converter.fromEntity(entity); + URI locationURI = getLocationURI(savedObject); return ResponseEntity - .created(getLocationURI(converter.fromEntity(entity))) + .created(locationURI) .build(); } @@ -152,6 +160,6 @@ protected URI getLocationURI(Pojo object) throws URISyntaxException { * Delete object from storage. Always return OK http status with empty body. */ public void delete(ID id) { - getById(id).ifPresent(repository::delete); + repository.deleteById(id); } } diff --git a/src/main/java/ru/investbook/api/SecurityRestController.java b/src/main/java/ru/investbook/api/SecurityRestController.java index 7ec8e135..78480b5c 100644 --- a/src/main/java/ru/investbook/api/SecurityRestController.java +++ b/src/main/java/ru/investbook/api/SecurityRestController.java @@ -65,7 +65,7 @@ public Page get(@Parameter(hidden = true) @Override @GetMapping("{id}") @Operation(summary = "Отобразить один", - description = "Отобразить биржевой инструмент по идентификатору (ISIN, коду дериватива, валютной пары)") + description = "Отобразить биржевой инструмент по внутреннему идентификатору") public ResponseEntity get(@PathVariable("id") @Parameter(description = "Идентификатор", example = "123", required = true) Integer id) { @@ -102,8 +102,8 @@ public void delete(@PathVariable("id") } @Override - protected Optional getById(Integer isin) { - return repository.findById(isin); + protected Optional getById(Integer id) { + return repository.findById(id); } @Override diff --git a/src/main/java/ru/investbook/service/cbr/AbstractCbrForeignExchangeRateService.java b/src/main/java/ru/investbook/service/cbr/AbstractCbrForeignExchangeRateService.java index fc9bef10..d9b2625e 100644 --- a/src/main/java/ru/investbook/service/cbr/AbstractCbrForeignExchangeRateService.java +++ b/src/main/java/ru/investbook/service/cbr/AbstractCbrForeignExchangeRateService.java @@ -78,7 +78,7 @@ protected void save(ForeignExchangeRate fxRate) { ForeignExchangeRateEntity entity = foreignExchangeRateConverter.toEntity(fxRate); foreignExchangeRateRepository.save(entity); } catch (Exception e) { - log.debug("Ошибка сохранения {}, может быть запись уже существует?", fxRate); + log.debug("Ошибка сохранения {}", fxRate); } } } From c19166a416c3456045791ea3e8c47734daa07395 Mon Sep 17 00:00:00 2001 From: vananiev Date: Wed, 22 May 2024 01:24:47 +0300 Subject: [PATCH 18/40] replace saveAndFlush() in transaction by save() in transaction --- .../api/AbstractRestController.java | 16 ++++----- .../parser/SecurityRegistrarImpl.java | 12 +++---- .../AbstractSecurityAwareInvestbookTable.java | 2 +- ...bookSecurityDepositAndWithdrawalTable.java | 2 +- .../service/EventCashFlowFormsService.java | 8 ++--- .../ForeignExchangeRateFormsService.java | 2 +- .../service/PortfolioCashFormsService.java | 6 ++-- .../PortfolioPropertyFormsService.java | 6 ++-- .../SecurityDescriptionFormsService.java | 2 +- .../SecurityEventCashFlowFormsService.java | 8 ++--- .../service/SecurityQuoteFormsService.java | 4 +-- .../service/SecurityRepositoryHelper.java | 36 +++++++++---------- .../service/TransactionFormsService.java | 22 ++++++------ 13 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/main/java/ru/investbook/api/AbstractRestController.java b/src/main/java/ru/investbook/api/AbstractRestController.java index ed7f0724..e30c5409 100644 --- a/src/main/java/ru/investbook/api/AbstractRestController.java +++ b/src/main/java/ru/investbook/api/AbstractRestController.java @@ -76,7 +76,7 @@ protected ResponseEntity post(Pojo object) { try { ID id = getId(object); if (id == null) { - return createOrUpdateEntity(object); + return createOrUpdateEntityAndReturnCreateStatus(object); } if (existsById(id)) { return ResponseEntity @@ -87,7 +87,7 @@ protected ResponseEntity post(Pojo object) { // Если убрать @Transactional над методом, то следующая строка может обновить объект по ошибке. // Это возможно, если объект был создан другим потоком после проверки существования строки по ID. // По этой причине @Transactional убирать не нужно. - return createOrUpdateEntity(object); + return createOrUpdateEntityAndReturnCreateStatus(object); } } catch (Exception e) { throw new InternalServerErrorException("Не могу создать объект", e); @@ -117,10 +117,10 @@ public ResponseEntity put(ID id, Pojo object) { "запроса [" + getId(object) + "] не совпадают"); } if (existsById(id)) { - saveAndFlush(object); + createOrUpdateEntity(object); return ResponseEntity.ok().build(); } else { - return createOrUpdateEntity(object); + return createOrUpdateEntityAndReturnCreateStatus(object); } } catch (Exception e) { throw new InternalServerErrorException("Не могу создать объект", e); @@ -133,16 +133,16 @@ private boolean existsById(ID id) { protected abstract Pojo updateId(ID id, Pojo object); - private Entity saveAndFlush(Pojo object) { + private Entity createOrUpdateEntity(Pojo object) { Entity entity = converter.toEntity(object); - return repository.saveAndFlush(entity); + return repository.save(entity); } /** * @return response entity with http CREATE status, Location http header and body */ - private ResponseEntity createOrUpdateEntity(Pojo object) throws URISyntaxException { - Entity entity = saveAndFlush(object); + private ResponseEntity createOrUpdateEntityAndReturnCreateStatus(Pojo object) throws URISyntaxException { + Entity entity = createOrUpdateEntity(object); Pojo savedObject = converter.fromEntity(entity); URI locationURI = getLocationURI(savedObject); return ResponseEntity diff --git a/src/main/java/ru/investbook/parser/SecurityRegistrarImpl.java b/src/main/java/ru/investbook/parser/SecurityRegistrarImpl.java index 7743a4bf..118fa623 100644 --- a/src/main/java/ru/investbook/parser/SecurityRegistrarImpl.java +++ b/src/main/java/ru/investbook/parser/SecurityRegistrarImpl.java @@ -127,7 +127,7 @@ private int declareSecurityByIsin(String isin, SecurityType defaultType, Supplie return repository.findByIsin(isin) .or(() -> Optional.of(supplier.get()) .map(builder -> buildSecurity(builder, defaultType)) - .map(security -> saveAndFlush(security, () -> repository.findByIsin(isin)))) + .map(security -> save(security, () -> repository.findByIsin(isin)))) .map(SecurityEntity::getId) .orElseThrow(() -> new RuntimeException("Не смог сохранить ЦБ с ISIN = " + isin)); } @@ -136,7 +136,7 @@ private Integer declareSecurityByName(String name, SecurityType defaultType, Sup return repository.findByName(name) .or(() -> Optional.of(supplier.get()) .map(builder -> buildSecurity(builder, defaultType)) - .map(security -> saveAndFlush(security, () -> repository.findByName(name)))) + .map(security -> save(security, () -> repository.findByName(name)))) .map(SecurityEntity::getId) .orElseThrow(() -> new RuntimeException("Не смог сохранить актив с наименованием = " + name)); } @@ -145,7 +145,7 @@ private Integer declareSecurityByTicker(String ticker, SecurityType defaultType, return repository.findByTicker(ticker) .or(() -> Optional.of(supplier.get()) .map(builder -> buildSecurity(builder, defaultType)) - .map(security -> saveAndFlush(security, () -> repository.findByTicker(ticker)))) + .map(security -> save(security, () -> repository.findByTicker(ticker)))) .map(SecurityEntity::getId) .orElseThrow(() -> new RuntimeException("Не смог сохранить актив с тикером = " + ticker)); } @@ -153,7 +153,7 @@ private Integer declareSecurityByTicker(String ticker, SecurityType defaultType, private Integer declareContractByTicker(String contract, SecurityType contractType) { return repository.findByTicker(contract) .or(() -> Optional.of(Security.builder().ticker(contract).type(contractType).build()) - .map(security -> saveAndFlush(security, () -> repository.findByTicker(contract)))) + .map(security -> save(security, () -> repository.findByTicker(contract)))) .map(SecurityEntity::getId) .orElseThrow(() -> new RuntimeException("Не смог сохранить контракт = " + contract)); } @@ -166,10 +166,10 @@ private Security buildSecurity(SecurityBuilder builder, SecurityType defaultType return security; } - private SecurityEntity saveAndFlush(Security security, Supplier> supplier) { + private SecurityEntity save(Security security, Supplier> supplier) { try { validator.validate(security); - return repository.saveAndFlush(converter.toEntity(security)); + return repository.save(converter.toEntity(security)); } catch (ConstraintViolationException e) { throw new RuntimeException("Не смог сохранить ценную бумагу в БД: " + security + ", " + e.getMessage()); } catch (Exception e) { diff --git a/src/main/java/ru/investbook/parser/investbook/AbstractSecurityAwareInvestbookTable.java b/src/main/java/ru/investbook/parser/investbook/AbstractSecurityAwareInvestbookTable.java index d53ca61a..583c6b0a 100644 --- a/src/main/java/ru/investbook/parser/investbook/AbstractSecurityAwareInvestbookTable.java +++ b/src/main/java/ru/investbook/parser/investbook/AbstractSecurityAwareInvestbookTable.java @@ -86,7 +86,7 @@ private SecurityEntity createStockOrBond(String securityTickerNameOrIsin, Securi } else { builder.name(securityTickerNameOrIsin); } - return securityRepository.saveAndFlush(securityConverter.toEntity(builder.build())); + return securityRepository.save(securityConverter.toEntity(builder.build())); } protected String getTradeId(String portfolio, int securityId, Instant instant) { diff --git a/src/main/java/ru/investbook/parser/investbook/InvestbookSecurityDepositAndWithdrawalTable.java b/src/main/java/ru/investbook/parser/investbook/InvestbookSecurityDepositAndWithdrawalTable.java index 61fa39c8..b49700d1 100644 --- a/src/main/java/ru/investbook/parser/investbook/InvestbookSecurityDepositAndWithdrawalTable.java +++ b/src/main/java/ru/investbook/parser/investbook/InvestbookSecurityDepositAndWithdrawalTable.java @@ -78,6 +78,6 @@ private SecurityEntity createStockOrBond(String securityTickerNameOrIsin) { .type(SecurityType.STOCK_OR_BOND) .name(securityTickerNameOrIsin) .build(); - return securityRepository.saveAndFlush(securityConverter.toEntity(security)); + return securityRepository.save(securityConverter.toEntity(security)); } } diff --git a/src/main/java/ru/investbook/web/forms/service/EventCashFlowFormsService.java b/src/main/java/ru/investbook/web/forms/service/EventCashFlowFormsService.java index e46ab0e3..86ad137f 100644 --- a/src/main/java/ru/investbook/web/forms/service/EventCashFlowFormsService.java +++ b/src/main/java/ru/investbook/web/forms/service/EventCashFlowFormsService.java @@ -81,7 +81,7 @@ public Page getPage(EventCashFlowFormFilterModel filter) { @Transactional public void save(EventCashFlowModel e) { - saveAndFlush(e.getPortfolio()); + savePortfolio(e.getPortfolio()); if (e.isAttachedToSecurity()) { saveSecurityEventCashFlow(e); } else { @@ -90,7 +90,7 @@ public void save(EventCashFlowModel e) { } private void saveSecurityEventCashFlow(EventCashFlowModel e) { - int savedSecurityId = securityRepositoryHelper.saveAndFlushSecurity(requireNonNull(e.getAttachedSecurity())); + int savedSecurityId = securityRepositoryHelper.saveSecurity(requireNonNull(e.getAttachedSecurity())); SecurityEventCashFlowEntity entity = securityEventCashFlowRepository.save( securityEventCashFlowConverter.toEntity(SecurityEventCashFlow.builder() // no id(), it is always the new object @@ -123,9 +123,9 @@ private void saveEventCashFlow(EventCashFlowModel e) { eventCashFlowRepository.flush(); } - private void saveAndFlush(String portfolio) { + private void savePortfolio(String portfolio) { if (!portfolioRepository.existsById(portfolio)) { - portfolioRepository.saveAndFlush( + portfolioRepository.save( portfolioConverter.toEntity(Portfolio.builder() .id(portfolio) .build())); diff --git a/src/main/java/ru/investbook/web/forms/service/ForeignExchangeRateFormsService.java b/src/main/java/ru/investbook/web/forms/service/ForeignExchangeRateFormsService.java index 95a8a633..ac8ef907 100644 --- a/src/main/java/ru/investbook/web/forms/service/ForeignExchangeRateFormsService.java +++ b/src/main/java/ru/investbook/web/forms/service/ForeignExchangeRateFormsService.java @@ -69,7 +69,7 @@ public Page getPage(ForeignExchangeRateFormFilterModel @Transactional public void save(ForeignExchangeRateModel m) { - foreignExchangeRateRepository.saveAndFlush( + foreignExchangeRateRepository.save( foreignExchangeRateConverter.toEntity(ForeignExchangeRate.builder() .date(m.getDate()) .currencyPair((m.getBaseCurrency() + m.getQuoteCurrency()).toUpperCase()) diff --git a/src/main/java/ru/investbook/web/forms/service/PortfolioCashFormsService.java b/src/main/java/ru/investbook/web/forms/service/PortfolioCashFormsService.java index 96e26c34..a5ddeac8 100644 --- a/src/main/java/ru/investbook/web/forms/service/PortfolioCashFormsService.java +++ b/src/main/java/ru/investbook/web/forms/service/PortfolioCashFormsService.java @@ -75,7 +75,7 @@ public Page getPage(PortfolioCashFormFilterModel filter) { @Transactional public void save(PortfolioCashModel m) { - saveAndFlush(m.getPortfolio()); + savePortfolio(m.getPortfolio()); PortfolioCash cash = PortfolioCash.builder() .id(m.getId()) .portfolio(m.getPortfolio()) @@ -91,9 +91,9 @@ public void save(PortfolioCashModel m) { portfolioCashRepository.flush(); } - private void saveAndFlush(String portfolio) { + private void savePortfolio(String portfolio) { if (!portfolioRepository.existsById(portfolio)) { - portfolioRepository.saveAndFlush( + portfolioRepository.save( portfolioConverter.toEntity(Portfolio.builder() .id(portfolio) .build())); diff --git a/src/main/java/ru/investbook/web/forms/service/PortfolioPropertyFormsService.java b/src/main/java/ru/investbook/web/forms/service/PortfolioPropertyFormsService.java index ce2394f9..7e76089d 100644 --- a/src/main/java/ru/investbook/web/forms/service/PortfolioPropertyFormsService.java +++ b/src/main/java/ru/investbook/web/forms/service/PortfolioPropertyFormsService.java @@ -81,7 +81,7 @@ public Page getPage(PortfolioPropertyFormFilterModel fil @Transactional public void save(PortfolioPropertyModel m) { - saveAndFlush(m.getPortfolio()); + savePortfolio(m.getPortfolio()); PortfolioProperty.PortfolioPropertyBuilder builder = PortfolioProperty.builder() .id(m.getId()) .portfolio(m.getPortfolio()) @@ -100,9 +100,9 @@ public void save(PortfolioPropertyModel m) { portfolioPropertyRepository.flush(); } - private void saveAndFlush(String portfolio) { + private void savePortfolio(String portfolio) { if (!portfolioRepository.existsById(portfolio)) { - portfolioRepository.saveAndFlush( + portfolioRepository.save( portfolioConverter.toEntity(Portfolio.builder() .id(portfolio) .build())); diff --git a/src/main/java/ru/investbook/web/forms/service/SecurityDescriptionFormsService.java b/src/main/java/ru/investbook/web/forms/service/SecurityDescriptionFormsService.java index fbbfc768..867b1638 100644 --- a/src/main/java/ru/investbook/web/forms/service/SecurityDescriptionFormsService.java +++ b/src/main/java/ru/investbook/web/forms/service/SecurityDescriptionFormsService.java @@ -67,7 +67,7 @@ public Page getPage(SecurityDescriptionFormFilterModel @Transactional public void save(SecurityDescriptionModel m) { - int savedSecurityId = securityRepositoryHelper.saveAndFlushSecurity(m); + int savedSecurityId = securityRepositoryHelper.saveSecurity(m); securityDescriptionRepository.createOrUpdateSector(savedSecurityId, m.getSector()); } diff --git a/src/main/java/ru/investbook/web/forms/service/SecurityEventCashFlowFormsService.java b/src/main/java/ru/investbook/web/forms/service/SecurityEventCashFlowFormsService.java index 5909bcf1..10595226 100644 --- a/src/main/java/ru/investbook/web/forms/service/SecurityEventCashFlowFormsService.java +++ b/src/main/java/ru/investbook/web/forms/service/SecurityEventCashFlowFormsService.java @@ -79,8 +79,8 @@ public Page getPage(SecurityEventCashFlowFormFilterM @Transactional public void save(SecurityEventCashFlowModel e) { - saveAndFlush(e.getPortfolio()); - int savedSecurityId = securityRepositoryHelper.saveAndFlushSecurity(e); + savePortfolio(e.getPortfolio()); + int savedSecurityId = securityRepositoryHelper.saveSecurity(e); SecurityEventCashFlowBuilder builder = SecurityEventCashFlow.builder() .portfolio(e.getPortfolio()) .timestamp(e.getDate().atTime(e.getTime()).atZone(zoneId).toInstant()) @@ -109,9 +109,9 @@ public void save(SecurityEventCashFlowModel e) { securityEventCashFlowRepository.flush(); } - private void saveAndFlush(String portfolio) { + private void savePortfolio(String portfolio) { if (!portfolioRepository.existsById(portfolio)) { - portfolioRepository.saveAndFlush( + portfolioRepository.save( portfolioConverter.toEntity(Portfolio.builder() .id(portfolio) .build())); diff --git a/src/main/java/ru/investbook/web/forms/service/SecurityQuoteFormsService.java b/src/main/java/ru/investbook/web/forms/service/SecurityQuoteFormsService.java index a69effe9..11b33b0d 100644 --- a/src/main/java/ru/investbook/web/forms/service/SecurityQuoteFormsService.java +++ b/src/main/java/ru/investbook/web/forms/service/SecurityQuoteFormsService.java @@ -69,8 +69,8 @@ public Page getPage(SecurityQuoteFormFilterModel filter) { @Transactional public void save(SecurityQuoteModel e) { - int savedSecurityId = securityRepositoryHelper.saveAndFlushSecurity(e); - SecurityQuoteEntity entity = securityQuoteRepository.saveAndFlush( + int savedSecurityId = securityRepositoryHelper.saveSecurity(e); + SecurityQuoteEntity entity = securityQuoteRepository.save( securityQuoteConverter.toEntity(SecurityQuote.builder() .id(e.getId()) .security(savedSecurityId) diff --git a/src/main/java/ru/investbook/web/forms/service/SecurityRepositoryHelper.java b/src/main/java/ru/investbook/web/forms/service/SecurityRepositoryHelper.java index 44b33b14..87c430d8 100644 --- a/src/main/java/ru/investbook/web/forms/service/SecurityRepositoryHelper.java +++ b/src/main/java/ru/investbook/web/forms/service/SecurityRepositoryHelper.java @@ -52,8 +52,8 @@ public class SecurityRepositoryHelper { * Generate securityId if needed, save security to DB, update securityId for model if needed * @return saved security id */ - public int saveAndFlushSecurity(SecurityDescriptionModel m) { - int savedSecurityId = saveAndFlush(m.getSecurityId(), m.getSecurityIsin(), m.getSecurityName(), m.getSecurityType()); + public int saveSecurity(SecurityDescriptionModel m) { + int savedSecurityId = saveSecurity(m.getSecurityId(), m.getSecurityIsin(), m.getSecurityName(), m.getSecurityType()); m.setSecurityId(savedSecurityId); return savedSecurityId; } @@ -62,62 +62,62 @@ public int saveAndFlushSecurity(SecurityDescriptionModel m) { * Generate securityId if needed, save security to DB, update securityId for model if needed * @return saved security id */ - public int saveAndFlushSecurity(EventCashFlowModel.AttachedSecurity s) { + public int saveSecurity(EventCashFlowModel.AttachedSecurity s) { // EventCashFlowModel. AttachToSecurity может содержать только SHARE или BOND. // Тип SHARE захардкожен, тип может быть ошибочен, но для текущего алгоритма сохранения ЦБ этого типа достаточно - return saveAndFlush(s.getSecurityIsin(), s.getSecurityName(), SecurityType.SHARE); + return saveSecurity(s.getSecurityIsin(), s.getSecurityName(), SecurityType.SHARE); } /** * Generate securityId if needed, save security to DB, update securityId for model if needed * @return saved security id */ - public int saveAndFlushSecurity(SecurityEventCashFlowModel m) { - return saveAndFlush(m.getSecurityIsin(), m.getSecurityName(), m.getSecurityType()); + public int saveSecurity(SecurityEventCashFlowModel m) { + return saveSecurity(m.getSecurityIsin(), m.getSecurityName(), m.getSecurityType()); } /** * Generate securityId if needed, save security to DB, update securityId for model if needed * @return saved security id */ - public int saveAndFlushSecurity(SecurityQuoteModel m) { - return saveAndFlush(m.getSecurityIsin(), m.getSecurityName(), m.getSecurityType()); + public int saveSecurity(SecurityQuoteModel m) { + return saveSecurity(m.getSecurityIsin(), m.getSecurityName(), m.getSecurityType()); } /** * Generate securityId if needed, save security to DB, update securityId for model if needed * @return saved security id */ - public int saveAndFlushSecurity(TransactionModel m) { - return saveAndFlush(m.getSecurityIsin(), m.getSecurityName(), m.getSecurityType()); + public int saveSecurity(TransactionModel m) { + return saveSecurity(m.getSecurityIsin(), m.getSecurityName(), m.getSecurityType()); } /** * Generate securityId if needed, save security to DB, update securityId for model if needed * @return saved security id */ - public int saveAndFlushSecurity(SplitModel m) { - return saveAndFlush(m.getSecurityIsin(), m.getSecurityName(), SecurityType.SHARE); + public int saveSecurity(SplitModel m) { + return saveSecurity(m.getSecurityIsin(), m.getSecurityName(), SecurityType.SHARE); } /** * @return securityId from DB */ - private int saveAndFlush(Integer securityId, String isin, String securityName, SecurityType securityType) { + private int saveSecurity(Integer securityId, String isin, String securityName, SecurityType securityType) { SecurityEntity security = ofNullable(securityId) .flatMap(securityRepository::findById) .or(() -> findSecurity(isin, securityName, securityType)) .orElseGet(SecurityEntity::new); - return saveAndFlush(security, isin, securityName, securityType, true); + return saveSecurity(security, isin, securityName, securityType, true); } - private int saveAndFlush(String isin, String securityName, SecurityType securityType) { + private int saveSecurity(String isin, String securityName, SecurityType securityType) { SecurityEntity security = findSecurity(isin, securityName, securityType) .orElseGet(SecurityEntity::new); - return saveAndFlush(security, isin, securityName, securityType, false); + return saveSecurity(security, isin, securityName, securityType, false); } - private int saveAndFlush(SecurityEntity security, String isin, String securityName, + private int saveSecurity(SecurityEntity security, String isin, String securityName, SecurityType securityType, boolean forceRewriteByEmptyIsin) { isin = validateIsin(isin); if (isin == null && !forceRewriteByEmptyIsin) { @@ -140,7 +140,7 @@ private int saveAndFlush(SecurityEntity security, String isin, String securityNa security.setName(securityName); } } - security = securityRepository.saveAndFlush(security); + security = securityRepository.save(security); return security.getId(); } diff --git a/src/main/java/ru/investbook/web/forms/service/TransactionFormsService.java b/src/main/java/ru/investbook/web/forms/service/TransactionFormsService.java index 34c2f7ab..986fbd13 100644 --- a/src/main/java/ru/investbook/web/forms/service/TransactionFormsService.java +++ b/src/main/java/ru/investbook/web/forms/service/TransactionFormsService.java @@ -122,7 +122,7 @@ private Page getTransactionModels(Specification Date: Mon, 27 May 2024 00:25:19 +0300 Subject: [PATCH 19/40] update spring-boot to 3.3.0 --- README-en.md | 2 +- README.md | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README-en.md b/README-en.md index 86373d45..f45d38c8 100644 --- a/README-en.md +++ b/README-en.md @@ -2,7 +2,7 @@ [](README.md)
[![java-version](https://img.shields.io/badge/java-22-brightgreen?style=flat-square)](https://openjdk.org/) -[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.2.5-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) +[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.3.0-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) [![github-closed-pull-requests](https://img.shields.io/github/issues-pr-closed/spacious-team/investbook?style=flat-square&color=brightgreen)](https://github.com/spacious-team/investbook/pulls?q=is%3Apr+is%3Aclosed) [![github-workflow-status](https://img.shields.io/github/actions/workflow/status/spacious-team/investbook/publish-docker.yml?style=flat-square&branch=master)](https://github.com/spacious-team/investbook/actions/workflows/publish-docker.yml) diff --git a/README.md b/README.md index 915948f9..22df5b2a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [](README.md)
[![java-version](https://img.shields.io/badge/java-22-brightgreen?style=flat-square)](https://openjdk.org/) -[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.2.5-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) +[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.3.0-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) [![github-closed-pull-requests](https://img.shields.io/github/issues-pr-closed/spacious-team/investbook?style=flat-square&color=brightgreen)](https://github.com/spacious-team/investbook/pulls?q=is%3Apr+is%3Aclosed) [![github-workflow-status](https://img.shields.io/github/actions/workflow/status/spacious-team/investbook/publish-docker.yml?style=flat-square&branch=master)](https://github.com/spacious-team/investbook/actions/workflows/publish-docker.yml) diff --git a/pom.xml b/pom.xml index 748f2853..514161a3 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.5 + 3.3.0 ru.investbook From 333ab29881658a738288134adb34a1fe258971e3 Mon Sep 17 00:00:00 2001 From: inav975 Date: Wed, 5 Jun 2024 01:15:58 +0300 Subject: [PATCH 20/40] Update README-en.md --- README-en.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README-en.md b/README-en.md index f45d38c8..c6ce3813 100644 --- a/README-en.md +++ b/README-en.md @@ -62,7 +62,7 @@ For each account separately and summing up a single total for all accounts, the - [portfolio](src/main/asciidoc/portfolio-status.adoc) of securities with information about the current position, - average price purchases and yield of securities (CHISTVNDOH/XIRR), taking into account hedging positions in the derivatives + average price purchases and yield of securities (XIRR), taking into account hedging positions in the derivatives market and the average purchase price of currency; ![portfolio](https://user-images.githubusercontent.com/11336712/104820094-af2dce80-5843-11eb-8083-6521ea537334.png) - share of a security in a [portfolio](src/main/asciidoc/portfolio-status.adoc); @@ -85,7 +85,7 @@ For each account separately and summing up a single total for all accounts, the ![foreign-market](https://user-images.githubusercontent.com/11336712/84881751-fa59e600-b096-11ea-8b83-19d1c1229d73.png) - [input and output](src/main/asciidoc/securities-deposit-and-withdrawal.adoc) of securities from/to other accounts, conversion, split of shares (AAPL, TSLA, etc.); -- [profitability](src/main/asciidoc/cash-flow.adoc) of portfolio (CHISTVNDOH/XIRR), replenishments, write-offs, transfers from/to other accounts, +- [profitability](src/main/asciidoc/cash-flow.adoc) of portfolio (XIRR), replenishments, write-offs, transfers from/to other accounts, - current cash balance; ![cash-in](https://user-images.githubusercontent.com/11336712/100395491-3172f100-3052-11eb-9652-cd5730ac2e6f.png) - [tax](src/main/asciidoc/tax.adoc) burden, including the From 89db16a2db83877eb65e45b647dcea46c920a618 Mon Sep 17 00:00:00 2001 From: rikottafreska Date: Wed, 12 Jun 2024 11:11:34 +0300 Subject: [PATCH 21/40] Update AssignedOrIdentityGenerator.java --- .../java/ru/investbook/entity/AssignedOrIdentityGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java b/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java index b3b14b45..46004081 100644 --- a/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java +++ b/src/main/java/ru/investbook/entity/AssignedOrIdentityGenerator.java @@ -24,7 +24,7 @@ /** * If Entity ID is not null, then this ID is stored to DB. - * If Entity ID is null, then RDBM should generate ID itself. + * If Entity ID is null, then RDBMS should generate ID itself. *

* In other words: this generator behaves like {@link org.hibernate.id.Assigned} generator if entity's field, * marked by {@link Id} annotation, is not null, or like {@link org.hibernate.id.IdentityGenerator} if this field is null. From b31a9a1bc3adf326c80eb597a5e81b933f60b15a Mon Sep 17 00:00:00 2001 From: vananiev Date: Sat, 20 Jul 2024 15:34:06 +0300 Subject: [PATCH 22/40] add EntityRepositoryService --- .../api/AbstractEntityRepositoryService.java | 151 ++++++++++++++++++ .../api/AbstractRestController.java | 87 ++++------ .../api/EntityRepositoryService.java | 78 +++++++++ .../api/EventCashFlowRestController.java | 7 - .../ForeignExchangeRateRestController.java | 19 +-- .../investbook/api/IssuerRestController.java | 7 - .../api/PortfolioCashRestController.java | 7 - .../api/PortfolioPropertyRestController.java | 7 - .../api/PortfolioRestController.java | 7 - .../SecurityDescriptionRestController.java | 7 - .../SecurityEventCashFlowRestController.java | 7 - .../api/SecurityQuoteRestController.java | 7 - .../api/SecurityRestController.java | 7 - .../TransactionCashFlowRestController.java | 6 - .../api/TransactionRestController.java | 6 - .../parser/InvestbookApiClient.java | 2 + 16 files changed, 268 insertions(+), 144 deletions(-) create mode 100644 src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java create mode 100644 src/main/java/ru/investbook/api/EntityRepositoryService.java diff --git a/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java b/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java new file mode 100644 index 00000000..6541e33a --- /dev/null +++ b/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java @@ -0,0 +1,151 @@ +/* + * InvestBook + * Copyright (C) 2024 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package ru.investbook.api; + +import jakarta.annotation.PostConstruct; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.Session; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; +import ru.investbook.converter.EntityConverter; + +import java.util.Optional; + +import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW; +import static ru.investbook.repository.RepositoryHelper.isUniqIndexViolationException; + +@Slf4j +@RequiredArgsConstructor +public abstract class AbstractEntityRepositoryService implements EntityRepositoryService { + private final JpaRepository repository; + private final EntityConverter converter; + @Autowired + private EntityManager entityManager; + @Autowired + private PlatformTransactionManager transactionManager; + private TransactionTemplate transactionTemplateRequired; + private TransactionTemplate transactionTemplateRequiresNew; + + @PostConstruct + void init() { + transactionTemplateRequired = new TransactionTemplate(transactionManager); + transactionTemplateRequiresNew = new TransactionTemplate(transactionManager); + transactionTemplateRequiresNew.setPropagationBehavior(PROPAGATION_REQUIRES_NEW); + } + + @Override + public boolean existsById(ID id) { + return repository.existsById(id); + } + + @Override + public Optional getById(ID id) { + return repository.findById(id) + .map(converter::fromEntity); + } + + @Override + public Page get(Pageable pageable) { + return repository.findAll(pageable) + .map(converter::fromEntity); + } + + @Override + @SuppressWarnings("deprecation") + public boolean insert(Pojo object) { + if (entityManager instanceof Session hibernateSpecificSession) { + try { + Entity entity = converter.toEntity(object); + transactionTemplateRequiresNew.executeWithoutResult(_ -> hibernateSpecificSession.save(entity)); + return true; + } catch (Exception e) { + if (isUniqIndexViolationException(e)) { + return false; // can't insert, object with same ID already exists in DB or unique key constraint violation + } + log.error("Can't INSERT by optimized deprecated Hibernate method save(): {}", object, e); + } + } + Boolean result = transactionTemplateRequired.execute(_ -> create(object)); + return Boolean.TRUE.equals(result); + } + + @Override + @Transactional + public boolean create(Pojo object) { + return createInternal(object) + .isPresent(); + } + + @Override + @Transactional + public Optional createAndGet(Pojo object) { + return createInternal(object) + .map(converter::fromEntity); + } + + /** + * Creates a new object (with SELECT check) + * + * @return created entity if object is created or empty Optional otherwise + * @implSpec Should be called in transaction + */ + private Optional createInternal(Pojo object) { + ID id = getId(object); + if (id != null && existsById(id)) { + return Optional.empty(); + } + // Если работать не в транзакции, то следующая строка может повторно создать объект. + // Это возможно, если объект был создан другим потоком после проверки существования строки по ID. + // Метод должен работать в транзакции. + Entity savedEntity = createOrUpdateInternal(object); + return Optional.of(savedEntity); + } + + @Override + @Transactional + public void createOrUpdate(Pojo object) { + createOrUpdateInternal(object); + } + + @Override + @Transactional + public Pojo createOrUpdateAndGet(Pojo object) { + Entity savedEntity = createOrUpdateInternal(object); + return converter.fromEntity(savedEntity); + } + + private Entity createOrUpdateInternal(Pojo object) { + Entity entity = converter.toEntity(object); + return repository.save(entity); + } + + @Override + public void deleteById(ID id) { + repository.deleteById(id); + } + + protected abstract ID getId(Pojo object); +} diff --git a/src/main/java/ru/investbook/api/AbstractRestController.java b/src/main/java/ru/investbook/api/AbstractRestController.java index e30c5409..a70b58b8 100644 --- a/src/main/java/ru/investbook/api/AbstractRestController.java +++ b/src/main/java/ru/investbook/api/AbstractRestController.java @@ -19,9 +19,7 @@ package ru.investbook.api; import jakarta.persistence.GeneratedValue; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; +import lombok.SneakyThrows; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -30,38 +28,28 @@ import ru.investbook.converter.EntityConverter; import java.net.URI; -import java.net.URISyntaxException; import java.util.Objects; -import java.util.Optional; import static java.nio.charset.StandardCharsets.UTF_8; -@RequiredArgsConstructor -public abstract class AbstractRestController { - protected final JpaRepository repository; - protected final EntityConverter converter; +public abstract class AbstractRestController extends AbstractEntityRepositoryService { - - protected Page get(Pageable pageable) { - return repository.findAll(pageable) - .map(converter::fromEntity); + protected AbstractRestController(JpaRepository repository, EntityConverter converter) { + super(repository, converter); } /** - * Get the entity. + * Gets the entity. * If entity not exists NOT_FOUND http status will be returned. */ public ResponseEntity get(ID id) { return getById(id) - .map(converter::fromEntity) .map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); } - protected abstract Optional getById(ID id); - /** - * Create a new entity. + * Creates a new entity. * If entity has ID and record with this ID already exists in DB, CONFLICT http status and Location header was returned. * Otherwise, CREATE http status will be returned with Location header. * When creating new object, ID may be passed, but that ID only used when no {@link GeneratedValue} set @@ -74,30 +62,19 @@ public ResponseEntity get(ID id) { @Transactional protected ResponseEntity post(Pojo object) { try { - ID id = getId(object); - if (id == null) { - return createOrUpdateEntityAndReturnCreateStatus(object); - } - if (existsById(id)) { - return ResponseEntity - .status(HttpStatus.CONFLICT) - .location(getLocationURI(object)) - .build(); - } else { - // Если убрать @Transactional над методом, то следующая строка может обновить объект по ошибке. - // Это возможно, если объект был создан другим потоком после проверки существования строки по ID. - // По этой причине @Transactional убирать не нужно. - return createOrUpdateEntityAndReturnCreateStatus(object); - } + return createAndGet(object) + .map(this::createResponseWithLocationHeader) + .orElseGet(() -> ResponseEntity + .status(HttpStatus.CONFLICT) + .location(getLocationURI(object)) + .build()); } catch (Exception e) { throw new InternalServerErrorException("Не могу создать объект", e); } } - protected abstract ID getId(Pojo object); - /** - * Update or create a new entity. + * Updates or creates a new entity. * In update case method returns OK http status. * In create case method returns CREATE http status and Location header. * When creating new object, ID may be passed in the object, but it should be same as {@code id} argument. @@ -116,50 +93,44 @@ public ResponseEntity put(ID id, Pojo object) { throw new BadRequestException("Идентификатор объекта, переданный в URI [" + id + "] и в теле " + "запроса [" + getId(object) + "] не совпадают"); } - if (existsById(id)) { - createOrUpdateEntity(object); - return ResponseEntity.ok().build(); + if (create(object)) { + return ResponseEntity.ok().build(); // todo no content } else { - return createOrUpdateEntityAndReturnCreateStatus(object); + Pojo savedObject = createOrUpdateAndGet(object); + return createResponseWithLocationHeader(savedObject); // todo change http status? } } catch (Exception e) { throw new InternalServerErrorException("Не могу создать объект", e); } } - private boolean existsById(ID id) { - return repository.existsById(id); - } - - protected abstract Pojo updateId(ID id, Pojo object); - - private Entity createOrUpdateEntity(Pojo object) { - Entity entity = converter.toEntity(object); - return repository.save(entity); + /** + * Deletes object from storage. Always return OK http status with empty body. + */ + // TODO should impl 404 status? https://stackoverflow.com/questions/4088350/is-rest-delete-really-idempotent + public void delete(ID id) { + deleteById(id); } /** * @return response entity with http CREATE status, Location http header and body */ - private ResponseEntity createOrUpdateEntityAndReturnCreateStatus(Pojo object) throws URISyntaxException { - Entity entity = createOrUpdateEntity(object); - Pojo savedObject = converter.fromEntity(entity); - URI locationURI = getLocationURI(savedObject); + private ResponseEntity createResponseWithLocationHeader(Pojo object) { + URI locationURI = getLocationURI(object); return ResponseEntity .created(locationURI) .build(); } - protected URI getLocationURI(Pojo object) throws URISyntaxException { + @SneakyThrows + protected URI getLocationURI(Pojo object) { return new URI(UriUtils.encodePath(getLocation() + "/" + getId(object), UTF_8)); } protected abstract String getLocation(); /** - * Delete object from storage. Always return OK http status with empty body. + * Returns new object with updated ID */ - public void delete(ID id) { - repository.deleteById(id); - } + protected abstract Pojo updateId(ID id, Pojo object); } diff --git a/src/main/java/ru/investbook/api/EntityRepositoryService.java b/src/main/java/ru/investbook/api/EntityRepositoryService.java new file mode 100644 index 00000000..0a9e46c8 --- /dev/null +++ b/src/main/java/ru/investbook/api/EntityRepositoryService.java @@ -0,0 +1,78 @@ +/* + * InvestBook + * Copyright (C) 2024 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package ru.investbook.api; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.Optional; + +public interface EntityRepositoryService { + + boolean existsById(ID id); + + Optional getById(ID id); + + Page get(Pageable pageable); + + /** + * Creates a new object with direct INSERT into DB (without prior SELECT call) if possible, + * calls {@link #create(Object)} otherwise. + * + * @return true if object is created or false otherwise + * @implNote Method performance is the same as {@link #create(Object)} for H2 2.2.224 and MariaDB 11.2 + */ + boolean insert(Pojo object); + + /** + * Creates new object, doesn't update. + * Calls SELECT to check if object's ID exists in DB. + * + * @return true if object was created, false if object with ID already exists or other INSERT error was occurred + * @see #insert(Object) + */ + boolean create(Pojo object); + + /** + * Creates new object, doesn't update. + * Calls SELECT to check if object's ID exists in DB. + * Use faster {@link #create(Object)} method if saved object is not required + * + * @return saved object or empty Optional if object with ID already exists or other INSERT error was occurred + * @see #create(Object) + */ + Optional createAndGet(Pojo object); + + /** + * Create new or update existing object in DB. + * Use instead of the slower method {@link #createOrUpdateAndGet(Object)} + * if saved object is not required + */ + void createOrUpdate(Pojo object); + + /** + * Create new or update existing object in DB. + * + * @return the saved object (some fields, such as ID, may be set by the DBMS) + * @see #createOrUpdate(Object) + */ + Pojo createOrUpdateAndGet(Pojo object); + + void deleteById(ID id); +} diff --git a/src/main/java/ru/investbook/api/EventCashFlowRestController.java b/src/main/java/ru/investbook/api/EventCashFlowRestController.java index b2fb9ce6..fb6fb35c 100644 --- a/src/main/java/ru/investbook/api/EventCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/EventCashFlowRestController.java @@ -39,8 +39,6 @@ import ru.investbook.converter.EntityConverter; import ru.investbook.entity.EventCashFlowEntity; -import java.util.Optional; - @RestController @Tag(name = "Движения ДС по счету", description = """ Ввод, вывод ДС, налоги, комиссии, а также дивиденды, купоны, амортизации по бумагам другого счета @@ -99,11 +97,6 @@ public void delete(@PathVariable("id") super.delete(id); } - @Override - protected Optional getById(Integer id) { - return repository.findById(id); - } - @Override protected Integer getId(EventCashFlow object) { return object.getId(); diff --git a/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java b/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java index f104bb2f..c67db81e 100644 --- a/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java +++ b/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java @@ -22,6 +22,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import lombok.SneakyThrows; import org.spacious_team.broker.pojo.ForeignExchangeRate; import org.springdoc.core.converters.models.PageableAsQueryParam; import org.springframework.data.domain.Page; @@ -43,10 +44,8 @@ import ru.investbook.repository.ForeignExchangeRateRepository; import java.net.URI; -import java.net.URISyntaxException; import java.time.LocalDate; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; @RestController @@ -54,6 +53,7 @@ @RequestMapping("/api/v1/foreign-exchange-rates") public class ForeignExchangeRateRestController extends AbstractRestController { private final ForeignExchangeRateRepository foreignExchangeRateRepository; + private final ForeignExchangeRateConverter foreignExchangeRateConverter; private final ForeignExchangeRateService foreignExchangeRateService; public ForeignExchangeRateRestController(ForeignExchangeRateRepository repository, @@ -61,6 +61,7 @@ public ForeignExchangeRateRestController(ForeignExchangeRateRepository repositor ForeignExchangeRateService foreignExchangeRateService) { super(repository, converter); this.foreignExchangeRateRepository = repository; + this.foreignExchangeRateConverter = converter; this.foreignExchangeRateService = foreignExchangeRateService; } @@ -68,8 +69,8 @@ public ForeignExchangeRateRestController(ForeignExchangeRateRepository repositor @GetMapping @PageableAsQueryParam @Operation(summary = "Отобразить все", description = "Отображает все загруженные в БД информацию по обменным курсам") - protected Page get(@Parameter(hidden = true) - Pageable pageable) { + public Page get(@Parameter(hidden = true) + Pageable pageable) { return super.get(pageable); } @@ -81,7 +82,7 @@ protected List get(@PathVariable("currency-pair") String currencyPair) { return foreignExchangeRateRepository.findByPkCurrencyPairOrderByPkDateDesc(currencyPair) .stream() - .map(converter::fromEntity) + .map(foreignExchangeRateConverter::fromEntity) .collect(Collectors.toList()); } @@ -143,11 +144,6 @@ public void delete(@PathVariable("currency-pair") super.delete(getId(currencyPair, date)); } - @Override - protected Optional getById(ForeignExchangeRateEntityPk id) { - return repository.findById(id); - } - @Override protected ForeignExchangeRateEntityPk getId(ForeignExchangeRate object) { return getId(object.getCurrencyPair(), object.getDate()); @@ -169,7 +165,8 @@ protected ForeignExchangeRate updateId(ForeignExchangeRateEntityPk id, ForeignEx } @Override - protected URI getLocationURI(ForeignExchangeRate object) throws URISyntaxException { + @SneakyThrows + protected URI getLocationURI(ForeignExchangeRate object) { return new URI(getLocation() + "/currency-pairs/" + object.getCurrencyPair() + "/dates/" + object.getDate()); } diff --git a/src/main/java/ru/investbook/api/IssuerRestController.java b/src/main/java/ru/investbook/api/IssuerRestController.java index f5ac24cb..f944dd5b 100644 --- a/src/main/java/ru/investbook/api/IssuerRestController.java +++ b/src/main/java/ru/investbook/api/IssuerRestController.java @@ -39,8 +39,6 @@ import ru.investbook.converter.EntityConverter; import ru.investbook.entity.IssuerEntity; -import java.util.Optional; - @RestController @Tag(name = "Эмитенты", description = "Информация об эмитентах") @RequestMapping("/api/v1/issuers") @@ -96,11 +94,6 @@ public void delete(@PathVariable("id") super.delete(id); } - @Override - protected Optional getById(Integer id) { - return repository.findById(id); - } - @Override protected Integer getId(Issuer object) { return object.getId(); diff --git a/src/main/java/ru/investbook/api/PortfolioCashRestController.java b/src/main/java/ru/investbook/api/PortfolioCashRestController.java index 676eff3f..6d07caa1 100644 --- a/src/main/java/ru/investbook/api/PortfolioCashRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioCashRestController.java @@ -39,8 +39,6 @@ import ru.investbook.converter.EntityConverter; import ru.investbook.entity.PortfolioCashEntity; -import java.util.Optional; - @RestController @Tag(name = "Информация по остатку денежных средств на счете") @RequestMapping("/api/v1/portfolio-cash") @@ -97,11 +95,6 @@ public void delete(@PathVariable("id") super.delete(id); } - @Override - protected Optional getById(Integer id) { - return repository.findById(id); - } - @Override protected Integer getId(PortfolioCash object) { return object.getId(); diff --git a/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java b/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java index 3c038197..3bd17ec4 100644 --- a/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java @@ -39,8 +39,6 @@ import ru.investbook.converter.EntityConverter; import ru.investbook.entity.PortfolioPropertyEntity; -import java.util.Optional; - @RestController @Tag(name = "Информация по счетам") @RequestMapping("/api/v1/portfolio-properties") @@ -97,11 +95,6 @@ public void delete(@PathVariable("id") super.delete(id); } - @Override - protected Optional getById(Integer id) { - return repository.findById(id); - } - @Override protected Integer getId(PortfolioProperty object) { return object.getId(); diff --git a/src/main/java/ru/investbook/api/PortfolioRestController.java b/src/main/java/ru/investbook/api/PortfolioRestController.java index 83af4c0d..04f4e700 100644 --- a/src/main/java/ru/investbook/api/PortfolioRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioRestController.java @@ -39,8 +39,6 @@ import ru.investbook.entity.PortfolioEntity; import ru.investbook.repository.PortfolioRepository; -import java.util.Optional; - @RestController @Tag(name = "Счета") @RequestMapping("/api/v1/portfolios") @@ -98,11 +96,6 @@ public void delete(@PathVariable("id") super.delete(id); } - @Override - protected Optional getById(String id) { - return repository.findById(id); - } - @Override protected String getId(Portfolio object) { return object.getId(); diff --git a/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java b/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java index 4f5c394c..40ebe653 100644 --- a/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java +++ b/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java @@ -39,8 +39,6 @@ import ru.investbook.entity.SecurityDescriptionEntity; import ru.investbook.repository.SecurityDescriptionRepository; -import java.util.Optional; - @RestController @Tag(name = "Информация по инструментам", description = "Сектор экономики, эмитент") @RequestMapping("/api/v1/security-descriptions") @@ -102,11 +100,6 @@ public void delete(@PathVariable("id") super.delete(id); } - @Override - protected Optional getById(Integer isin) { - return repository.findById(isin); - } - @Override protected Integer getId(SecurityDescription object) { return object.getSecurity(); diff --git a/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java b/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java index d4348a6f..63a43909 100644 --- a/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java @@ -40,8 +40,6 @@ import ru.investbook.entity.SecurityEventCashFlowEntity; import ru.investbook.report.FifoPositionsFactory; -import java.util.Optional; - import static org.spacious_team.broker.pojo.CashFlowType.REDEMPTION; @RestController @@ -108,11 +106,6 @@ public void delete(@PathVariable("id") super.delete(id); } - @Override - protected Optional getById(Integer id) { - return repository.findById(id); - } - @Override protected Integer getId(SecurityEventCashFlow object) { return object.getId(); diff --git a/src/main/java/ru/investbook/api/SecurityQuoteRestController.java b/src/main/java/ru/investbook/api/SecurityQuoteRestController.java index 24b6f784..538279eb 100644 --- a/src/main/java/ru/investbook/api/SecurityQuoteRestController.java +++ b/src/main/java/ru/investbook/api/SecurityQuoteRestController.java @@ -39,8 +39,6 @@ import ru.investbook.converter.EntityConverter; import ru.investbook.entity.SecurityQuoteEntity; -import java.util.Optional; - @RestController @Tag(name = "Котировки", description = "Котировки биржевых инструментов") @RequestMapping("/api/v1/security-quotes") @@ -98,11 +96,6 @@ public void delete(@PathVariable("id") super.delete(id); } - @Override - protected Optional getById(Integer id) { - return repository.findById(id); - } - @Override protected Integer getId(SecurityQuote object) { return object.getId(); diff --git a/src/main/java/ru/investbook/api/SecurityRestController.java b/src/main/java/ru/investbook/api/SecurityRestController.java index 78480b5c..7d7efb8a 100644 --- a/src/main/java/ru/investbook/api/SecurityRestController.java +++ b/src/main/java/ru/investbook/api/SecurityRestController.java @@ -39,8 +39,6 @@ import ru.investbook.entity.SecurityEntity; import ru.investbook.repository.SecurityRepository; -import java.util.Optional; - @RestController @Tag(name = "Инструменты", description = "Акции, облигации, деривативы и валютные пары") @RequestMapping("/api/v1/securities") @@ -101,11 +99,6 @@ public void delete(@PathVariable("id") super.delete(id); } - @Override - protected Optional getById(Integer id) { - return repository.findById(id); - } - @Override protected Integer getId(Security object) { return object.getId(); diff --git a/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java b/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java index 58b025cd..983d9a2e 100644 --- a/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java @@ -44,7 +44,6 @@ import ru.investbook.repository.TransactionCashFlowRepository; import java.util.List; -import java.util.Optional; @RestController @Tag(name = "Движения ДС по сделкам", description = "Уплаченные и вырученные суммы в сделках") @@ -144,11 +143,6 @@ public void delete(@PathVariable("id") super.delete(id); } - @Override - protected Optional getById(Integer id) { - return repository.findById(id); - } - @Override protected Integer getId(TransactionCashFlow object) { return object.getId(); diff --git a/src/main/java/ru/investbook/api/TransactionRestController.java b/src/main/java/ru/investbook/api/TransactionRestController.java index b1c44124..1e40e4ea 100644 --- a/src/main/java/ru/investbook/api/TransactionRestController.java +++ b/src/main/java/ru/investbook/api/TransactionRestController.java @@ -43,7 +43,6 @@ import ru.investbook.repository.TransactionRepository; import java.util.List; -import java.util.Optional; @RestController @Tag(name = "Сделки", description = "Операции купли/продажи биржевых инструментов") @@ -151,11 +150,6 @@ public void delete(@PathVariable("id") super.delete(id); } - @Override - protected Optional getById(Integer id) { - return repository.findById(id); - } - @Override protected Integer getId(Transaction object) { return object.getId(); diff --git a/src/main/java/ru/investbook/parser/InvestbookApiClient.java b/src/main/java/ru/investbook/parser/InvestbookApiClient.java index 4a4952ed..e0458bc9 100644 --- a/src/main/java/ru/investbook/parser/InvestbookApiClient.java +++ b/src/main/java/ru/investbook/parser/InvestbookApiClient.java @@ -120,6 +120,7 @@ public void addTransaction(AbstractTransaction transaction) { } private Optional getSavedTransactionId(AbstractTransaction transaction) { + // todo replace RestController with EntityRepositoryService return Optional.of(transactionRestController.get(transaction.getPortfolio(), transaction.getTradeId(), Pageable.unpaged()).getContent()) .filter(result -> result.size() == 1) .map(List::getFirst) @@ -196,6 +197,7 @@ public void addForeignExchangeRate(ForeignExchangeRate exchangeRate) { /** * @return true if new row was added, or it was already exists in DB, false - or error */ + // todo replace RestController with EntityRepositoryService private boolean handlePost(T object, Function> saver, String errorPrefix) { try { validator.validate(object); From 95918b0282af5aa3b091ad4d15a6fd2d1941436f Mon Sep 17 00:00:00 2001 From: vananiev Date: Sat, 20 Jul 2024 15:57:53 +0300 Subject: [PATCH 23/40] add AbstractEntityRepositoryServiceTest --- .../AbstractEntityRepositoryServiceTest.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/test/java/ru/investbook/api/AbstractEntityRepositoryServiceTest.java diff --git a/src/test/java/ru/investbook/api/AbstractEntityRepositoryServiceTest.java b/src/test/java/ru/investbook/api/AbstractEntityRepositoryServiceTest.java new file mode 100644 index 00000000..d410acfb --- /dev/null +++ b/src/test/java/ru/investbook/api/AbstractEntityRepositoryServiceTest.java @@ -0,0 +1,76 @@ +/* + * InvestBook + * Copyright (C) 2024 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package ru.investbook.api; + +import org.junit.jupiter.api.Test; +import org.spacious_team.broker.pojo.Security; +import org.spacious_team.broker.pojo.SecurityType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.time.Duration; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +/** + * Performance test + */ +@SpringBootTest(properties = "spring.datasource.url=jdbc:h2:mem:testdb;mode=mysql;non_keywords=value") +class AbstractEntityRepositoryServiceTest { + + @Autowired + private SecurityRestController service; + private final Random random = new Random(); + private final AtomicInteger i = new AtomicInteger(); + + @Test + void insert() { + test("insert()", service::insert, false); + test("insert()", service::insert, false); + test("insert()", service::insert, true); + test("insert()", service::insert, true); + } + + @Test + void create() { + test("create()", service::create, false); + test("create()", service::create, false); + test("create()", service::create, true); + test("create()", service::create, true); + } + + void test(String name, Consumer consumer, boolean isIdNull) { + long t0 = System.nanoTime(); + for (int i = 0; i < 1_000; i++) { + Security security = createSecurity(isIdNull); + consumer.accept(security); + } + String isIdNullMsg = isIdNull ? "without predefined ID" : "with always same ID (insert error)"; + System.out.println("Total time " + name + " (" + isIdNullMsg + "):" + Duration.ofNanos(System.nanoTime() - t0)); + } + + Security createSecurity(boolean isIdNull) { + return Security.builder() + .id(isIdNull ? null : 1) + .type(SecurityType.STOCK) + .name(String.valueOf(i.getAndIncrement())) + .build(); + } +} \ No newline at end of file From 5412be0bcf7fbe83d61a0197e108f42091e86fad Mon Sep 17 00:00:00 2001 From: vananiev Date: Mon, 22 Jul 2024 00:01:40 +0300 Subject: [PATCH 24/40] restore correct Created status for http PUT method, change OK to No Content for PUT update case --- .../api/AbstractRestController.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/ru/investbook/api/AbstractRestController.java b/src/main/java/ru/investbook/api/AbstractRestController.java index a70b58b8..5c4634e0 100644 --- a/src/main/java/ru/investbook/api/AbstractRestController.java +++ b/src/main/java/ru/investbook/api/AbstractRestController.java @@ -31,6 +31,7 @@ import java.util.Objects; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.nonNull; public abstract class AbstractRestController extends AbstractEntityRepositoryService { @@ -50,8 +51,8 @@ public ResponseEntity get(ID id) { /** * Creates a new entity. - * If entity has ID and record with this ID already exists in DB, CONFLICT http status and Location header was returned. - * Otherwise, CREATE http status will be returned with Location header. + * If entity has ID and record with this ID already exists in DB, "409 Conflict" http status and Location header was returned. + * Otherwise, "201 Created" http status will be returned with Location header. * When creating new object, ID may be passed, but that ID only used when no {@link GeneratedValue} set * on Entity ID field or if {@link GeneratedValue#generator} set to {@link org.hibernate.generator.BeforeExecutionGenerator}, * generator impl; otherwise ID, passed in object, will be ignored (see JPA impl). @@ -75,8 +76,8 @@ protected ResponseEntity post(Pojo object) { /** * Updates or creates a new entity. - * In update case method returns OK http status. - * In create case method returns CREATE http status and Location header. + * In create case method returns "201 Created" http status and Location header. + * In update case method returns "204 No Content" http status. * When creating new object, ID may be passed in the object, but it should be same as {@code id} argument. * * @param id updating or creating entity id @@ -87,18 +88,18 @@ protected ResponseEntity post(Pojo object) { @Transactional public ResponseEntity put(ID id, Pojo object) { try { - if (getId(object) == null) { - object = updateId(id, object); - } else if (!Objects.equals(id, getId(object))) { + ID objectId = getId(object); + if (nonNull(objectId) && !Objects.equals(id, objectId)) { throw new BadRequestException("Идентификатор объекта, переданный в URI [" + id + "] и в теле " + - "запроса [" + getId(object) + "] не совпадают"); - } - if (create(object)) { - return ResponseEntity.ok().build(); // todo no content - } else { - Pojo savedObject = createOrUpdateAndGet(object); - return createResponseWithLocationHeader(savedObject); // todo change http status? + "запроса [" + objectId + "] не совпадают"); } + Pojo objectWithId = nonNull(objectId) ? object : updateId(id, object); + return createAndGet(objectWithId) + .map(this::createResponseWithLocationHeader) + .orElseGet(() -> { + createOrUpdate(objectWithId); + return ResponseEntity.noContent().build(); + }); } catch (Exception e) { throw new InternalServerErrorException("Не могу создать объект", e); } From 43aa72724c6ada487b411c660d3619d86a467531 Mon Sep 17 00:00:00 2001 From: vananiev Date: Mon, 22 Jul 2024 00:14:19 +0300 Subject: [PATCH 25/40] return 204 No Content by http delete method --- src/main/java/ru/investbook/api/AbstractRestController.java | 6 +++--- .../java/ru/investbook/api/EventCashFlowRestController.java | 4 ++-- .../investbook/api/ForeignExchangeRateRestController.java | 4 ++-- src/main/java/ru/investbook/api/IssuerRestController.java | 4 ++-- .../java/ru/investbook/api/PortfolioCashRestController.java | 4 ++-- .../ru/investbook/api/PortfolioPropertyRestController.java | 4 ++-- .../java/ru/investbook/api/PortfolioRestController.java | 4 ++-- .../investbook/api/SecurityDescriptionRestController.java | 4 ++-- .../investbook/api/SecurityEventCashFlowRestController.java | 4 ++-- .../java/ru/investbook/api/SecurityQuoteRestController.java | 4 ++-- src/main/java/ru/investbook/api/SecurityRestController.java | 4 ++-- .../investbook/api/TransactionCashFlowRestController.java | 4 ++-- .../java/ru/investbook/api/TransactionRestController.java | 4 ++-- 13 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main/java/ru/investbook/api/AbstractRestController.java b/src/main/java/ru/investbook/api/AbstractRestController.java index 5c4634e0..1c5b66cd 100644 --- a/src/main/java/ru/investbook/api/AbstractRestController.java +++ b/src/main/java/ru/investbook/api/AbstractRestController.java @@ -106,11 +106,11 @@ public ResponseEntity put(ID id, Pojo object) { } /** - * Deletes object from storage. Always return OK http status with empty body. + * Deletes object from storage. Always return "204 No Content" http status with empty body. */ - // TODO should impl 404 status? https://stackoverflow.com/questions/4088350/is-rest-delete-really-idempotent - public void delete(ID id) { + public ResponseEntity delete(ID id) { deleteById(id); + return ResponseEntity.noContent().build(); } /** diff --git a/src/main/java/ru/investbook/api/EventCashFlowRestController.java b/src/main/java/ru/investbook/api/EventCashFlowRestController.java index fb6fb35c..a1c76265 100644 --- a/src/main/java/ru/investbook/api/EventCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/EventCashFlowRestController.java @@ -91,10 +91,10 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") @Operation(summary = "Удалить", description = "Удалить информацию из БД") - public void delete(@PathVariable("id") + public ResponseEntity delete(@PathVariable("id") @Parameter(description = "Номер события") Integer id) { - super.delete(id); + return super.delete(id); } @Override diff --git a/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java b/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java index c67db81e..80f91f39 100644 --- a/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java +++ b/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java @@ -133,7 +133,7 @@ public ResponseEntity put(@PathVariable("currency-pair") */ @DeleteMapping("/currency-pairs/{currency-pair}/dates/{date}") @Operation(summary = "Удалить", description = "Удаляет информацию о курсе из БД") - public void delete(@PathVariable("currency-pair") + public ResponseEntity delete(@PathVariable("currency-pair") @Parameter(description = "Валютная пара", example = "USDRUB") String currencyPair, @PathVariable("date") @@ -141,7 +141,7 @@ public void delete(@PathVariable("currency-pair") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) { foreignExchangeRateService.invalidateCache(); - super.delete(getId(currencyPair, date)); + return super.delete(getId(currencyPair, date)); } @Override diff --git a/src/main/java/ru/investbook/api/IssuerRestController.java b/src/main/java/ru/investbook/api/IssuerRestController.java index f944dd5b..e2211ef8 100644 --- a/src/main/java/ru/investbook/api/IssuerRestController.java +++ b/src/main/java/ru/investbook/api/IssuerRestController.java @@ -88,10 +88,10 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") @Operation(summary = "Удалить", description = "Удаляет сведения об эмитенте из БД") - public void delete(@PathVariable("id") + public ResponseEntity delete(@PathVariable("id") @Parameter(description = "Внутренний идентификатор эмитента") Integer id) { - super.delete(id); + return super.delete(id); } @Override diff --git a/src/main/java/ru/investbook/api/PortfolioCashRestController.java b/src/main/java/ru/investbook/api/PortfolioCashRestController.java index 6d07caa1..5d39a352 100644 --- a/src/main/java/ru/investbook/api/PortfolioCashRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioCashRestController.java @@ -89,10 +89,10 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") @Operation(summary = "Удалить") - public void delete(@PathVariable("id") + public ResponseEntity delete(@PathVariable("id") @Parameter(description = "Внутренний идентификатор записи") Integer id) { - super.delete(id); + return super.delete(id); } @Override diff --git a/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java b/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java index 3bd17ec4..0f2c016c 100644 --- a/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java @@ -89,10 +89,10 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") @Operation(summary = "Удалить") - public void delete(@PathVariable("id") + public ResponseEntity delete(@PathVariable("id") @Parameter(description = "Внутренний идентификатор записи") Integer id) { - super.delete(id); + return super.delete(id); } @Override diff --git a/src/main/java/ru/investbook/api/PortfolioRestController.java b/src/main/java/ru/investbook/api/PortfolioRestController.java index 04f4e700..6c5caa8a 100644 --- a/src/main/java/ru/investbook/api/PortfolioRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioRestController.java @@ -90,10 +90,10 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") @Operation(summary = "Удалить", description = "Удалить счет и все связанные с ним данные") - public void delete(@PathVariable("id") + public ResponseEntity delete(@PathVariable("id") @Parameter(description = "Номер счета") String id) { - super.delete(id); + return super.delete(id); } @Override diff --git a/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java b/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java index 40ebe653..0f74f195 100644 --- a/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java +++ b/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java @@ -94,10 +94,10 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") @Operation(summary = "Удалить", description = "Удалить информацию по инструменту") - public void delete(@PathVariable("id") + public ResponseEntity delete(@PathVariable("id") @Parameter(description = "Идентификатор", example = "123", required = true) Integer id) { - super.delete(id); + return super.delete(id); } @Override diff --git a/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java b/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java index 63a43909..98ebe98f 100644 --- a/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java @@ -99,11 +99,11 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") @Operation(summary = "Удалить", description = "Удалить информацию о выплате из БД") - public void delete(@PathVariable("id") + public ResponseEntity delete(@PathVariable("id") @Parameter(description = "Внутренний идентификатор выплаты в БД") Integer id) { positionsFactory.invalidateCache(); - super.delete(id); + return super.delete(id); } @Override diff --git a/src/main/java/ru/investbook/api/SecurityQuoteRestController.java b/src/main/java/ru/investbook/api/SecurityQuoteRestController.java index 538279eb..227ed206 100644 --- a/src/main/java/ru/investbook/api/SecurityQuoteRestController.java +++ b/src/main/java/ru/investbook/api/SecurityQuoteRestController.java @@ -90,10 +90,10 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") @Operation(summary = "Удалить") - public void delete(@PathVariable("id") + public ResponseEntity delete(@PathVariable("id") @Parameter(description = "Номер записи о котировке") Integer id) { - super.delete(id); + return super.delete(id); } @Override diff --git a/src/main/java/ru/investbook/api/SecurityRestController.java b/src/main/java/ru/investbook/api/SecurityRestController.java index 7d7efb8a..05e9594c 100644 --- a/src/main/java/ru/investbook/api/SecurityRestController.java +++ b/src/main/java/ru/investbook/api/SecurityRestController.java @@ -93,10 +93,10 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") @Operation(summary = "Удалить", description = "Удалить сведения о биржевом инструменте и всех его сделках по всем счетам") - public void delete(@PathVariable("id") + public ResponseEntity delete(@PathVariable("id") @Parameter(description = "Идентификатор", example = "123", required = true) Integer id) { - super.delete(id); + return super.delete(id); } @Override diff --git a/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java b/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java index 983d9a2e..8be6772e 100644 --- a/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java @@ -137,10 +137,10 @@ public ResponseEntity put(@PathVariable("id") @Operation(summary = "Удалить", description = """ Удалить информацию об об объемах движения ДС по сделке. Сама сделка не удаляется, ее нужно удалить своим API """) - public void delete(@PathVariable("id") + public ResponseEntity delete(@PathVariable("id") @Parameter(description = "Внутренний идентификатор сделки") Integer id) { - super.delete(id); + return super.delete(id); } @Override diff --git a/src/main/java/ru/investbook/api/TransactionRestController.java b/src/main/java/ru/investbook/api/TransactionRestController.java index 1e40e4ea..fad619cd 100644 --- a/src/main/java/ru/investbook/api/TransactionRestController.java +++ b/src/main/java/ru/investbook/api/TransactionRestController.java @@ -143,11 +143,11 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") @Operation(summary = "Удалить", description = "Удаляет указанную сделку") - public void delete(@PathVariable("id") + public ResponseEntity delete(@PathVariable("id") @Parameter(description = "Внутренний идентификатор сделки") Integer id) { positionsFactory.invalidateCache(); - super.delete(id); + return super.delete(id); } @Override From 8300dfd29c5f26cac95fcb3f94f453c6768a56d4 Mon Sep 17 00:00:00 2001 From: vananiev Date: Fri, 26 Jul 2024 18:54:26 +0300 Subject: [PATCH 26/40] change EntityRepositoryService interface --- .../api/AbstractEntityRepositoryService.java | 4 +--- .../investbook/api/AbstractRestController.java | 6 ++++++ .../investbook/api/EntityRepositoryService.java | 16 ++++++++++++---- .../api/EventCashFlowRestController.java | 2 +- .../api/ForeignExchangeRateRestController.java | 2 +- .../ru/investbook/api/IssuerRestController.java | 2 +- .../api/PortfolioCashRestController.java | 2 +- .../api/PortfolioPropertyRestController.java | 2 +- .../investbook/api/PortfolioRestController.java | 2 +- .../api/SecurityDescriptionRestController.java | 2 +- .../api/SecurityEventCashFlowRestController.java | 2 +- .../api/SecurityQuoteRestController.java | 2 +- .../investbook/api/SecurityRestController.java | 2 +- .../api/TransactionCashFlowRestController.java | 2 +- .../api/TransactionRestController.java | 2 +- 15 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java b/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java index 6541e33a..61652ae6 100644 --- a/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java +++ b/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java @@ -68,7 +68,7 @@ public Optional getById(ID id) { } @Override - public Page get(Pageable pageable) { + public Page getPage(Pageable pageable) { return repository.findAll(pageable) .map(converter::fromEntity); } @@ -146,6 +146,4 @@ private Entity createOrUpdateInternal(Pojo object) { public void deleteById(ID id) { repository.deleteById(id); } - - protected abstract ID getId(Pojo object); } diff --git a/src/main/java/ru/investbook/api/AbstractRestController.java b/src/main/java/ru/investbook/api/AbstractRestController.java index 1c5b66cd..f5092568 100644 --- a/src/main/java/ru/investbook/api/AbstractRestController.java +++ b/src/main/java/ru/investbook/api/AbstractRestController.java @@ -20,6 +20,8 @@ import jakarta.persistence.GeneratedValue; import lombok.SneakyThrows; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -39,6 +41,10 @@ protected AbstractRestController(JpaRepository repository, EntityCon super(repository, converter); } + public Page get(Pageable pageable) { + return getPage(pageable); + } + /** * Gets the entity. * If entity not exists NOT_FOUND http status will be returned. diff --git a/src/main/java/ru/investbook/api/EntityRepositoryService.java b/src/main/java/ru/investbook/api/EntityRepositoryService.java index 0a9e46c8..6c51c47c 100644 --- a/src/main/java/ru/investbook/api/EntityRepositoryService.java +++ b/src/main/java/ru/investbook/api/EntityRepositoryService.java @@ -25,17 +25,20 @@ public interface EntityRepositoryService { + ID getId(Pojo object); + boolean existsById(ID id); Optional getById(ID id); - Page get(Pageable pageable); + Page getPage(Pageable pageable); /** * Creates a new object with direct INSERT into DB (without prior SELECT call) if possible, * calls {@link #create(Object)} otherwise. * - * @return true if object is created or false otherwise + * @return true if object is created, false if object with ID already exists + * @throws RuntimeException if an INSERT error occurs * @implNote Method performance is the same as {@link #create(Object)} for H2 2.2.224 and MariaDB 11.2 */ boolean insert(Pojo object); @@ -44,7 +47,8 @@ public interface EntityRepositoryService { * Creates new object, doesn't update. * Calls SELECT to check if object's ID exists in DB. * - * @return true if object was created, false if object with ID already exists or other INSERT error was occurred + * @return true if object was created, false if object with ID already exists + * @throws RuntimeException if an INSERT error occurs * @see #insert(Object) */ boolean create(Pojo object); @@ -54,7 +58,8 @@ public interface EntityRepositoryService { * Calls SELECT to check if object's ID exists in DB. * Use faster {@link #create(Object)} method if saved object is not required * - * @return saved object or empty Optional if object with ID already exists or other INSERT error was occurred + * @return created object or empty Optional if object with ID already exists + * @throws RuntimeException if an INSERT error occurs * @see #create(Object) */ Optional createAndGet(Pojo object); @@ -63,6 +68,8 @@ public interface EntityRepositoryService { * Create new or update existing object in DB. * Use instead of the slower method {@link #createOrUpdateAndGet(Object)} * if saved object is not required + * + * @throws RuntimeException if underlying DB error occurs */ void createOrUpdate(Pojo object); @@ -70,6 +77,7 @@ public interface EntityRepositoryService { * Create new or update existing object in DB. * * @return the saved object (some fields, such as ID, may be set by the DBMS) + * @throws RuntimeException if underlying DB error occurs * @see #createOrUpdate(Object) */ Pojo createOrUpdateAndGet(Pojo object); diff --git a/src/main/java/ru/investbook/api/EventCashFlowRestController.java b/src/main/java/ru/investbook/api/EventCashFlowRestController.java index a1c76265..236dbbae 100644 --- a/src/main/java/ru/investbook/api/EventCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/EventCashFlowRestController.java @@ -98,7 +98,7 @@ public ResponseEntity delete(@PathVariable("id") } @Override - protected Integer getId(EventCashFlow object) { + public Integer getId(EventCashFlow object) { return object.getId(); } diff --git a/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java b/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java index 80f91f39..8e962a79 100644 --- a/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java +++ b/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java @@ -145,7 +145,7 @@ public ResponseEntity delete(@PathVariable("currency-pair") } @Override - protected ForeignExchangeRateEntityPk getId(ForeignExchangeRate object) { + public ForeignExchangeRateEntityPk getId(ForeignExchangeRate object) { return getId(object.getCurrencyPair(), object.getDate()); } diff --git a/src/main/java/ru/investbook/api/IssuerRestController.java b/src/main/java/ru/investbook/api/IssuerRestController.java index e2211ef8..6a6ccedb 100644 --- a/src/main/java/ru/investbook/api/IssuerRestController.java +++ b/src/main/java/ru/investbook/api/IssuerRestController.java @@ -95,7 +95,7 @@ public ResponseEntity delete(@PathVariable("id") } @Override - protected Integer getId(Issuer object) { + public Integer getId(Issuer object) { return object.getId(); } diff --git a/src/main/java/ru/investbook/api/PortfolioCashRestController.java b/src/main/java/ru/investbook/api/PortfolioCashRestController.java index 5d39a352..b74650ef 100644 --- a/src/main/java/ru/investbook/api/PortfolioCashRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioCashRestController.java @@ -96,7 +96,7 @@ public ResponseEntity delete(@PathVariable("id") } @Override - protected Integer getId(PortfolioCash object) { + public Integer getId(PortfolioCash object) { return object.getId(); } diff --git a/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java b/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java index 0f2c016c..efe5fa4c 100644 --- a/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java @@ -96,7 +96,7 @@ public ResponseEntity delete(@PathVariable("id") } @Override - protected Integer getId(PortfolioProperty object) { + public Integer getId(PortfolioProperty object) { return object.getId(); } diff --git a/src/main/java/ru/investbook/api/PortfolioRestController.java b/src/main/java/ru/investbook/api/PortfolioRestController.java index 6c5caa8a..f9637127 100644 --- a/src/main/java/ru/investbook/api/PortfolioRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioRestController.java @@ -97,7 +97,7 @@ public ResponseEntity delete(@PathVariable("id") } @Override - protected String getId(Portfolio object) { + public String getId(Portfolio object) { return object.getId(); } diff --git a/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java b/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java index 0f74f195..1112b9ee 100644 --- a/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java +++ b/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java @@ -101,7 +101,7 @@ public ResponseEntity delete(@PathVariable("id") } @Override - protected Integer getId(SecurityDescription object) { + public Integer getId(SecurityDescription object) { return object.getSecurity(); } diff --git a/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java b/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java index 98ebe98f..9d5edbf9 100644 --- a/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java @@ -107,7 +107,7 @@ public ResponseEntity delete(@PathVariable("id") } @Override - protected Integer getId(SecurityEventCashFlow object) { + public Integer getId(SecurityEventCashFlow object) { return object.getId(); } diff --git a/src/main/java/ru/investbook/api/SecurityQuoteRestController.java b/src/main/java/ru/investbook/api/SecurityQuoteRestController.java index 227ed206..bac27a4c 100644 --- a/src/main/java/ru/investbook/api/SecurityQuoteRestController.java +++ b/src/main/java/ru/investbook/api/SecurityQuoteRestController.java @@ -97,7 +97,7 @@ public ResponseEntity delete(@PathVariable("id") } @Override - protected Integer getId(SecurityQuote object) { + public Integer getId(SecurityQuote object) { return object.getId(); } diff --git a/src/main/java/ru/investbook/api/SecurityRestController.java b/src/main/java/ru/investbook/api/SecurityRestController.java index 05e9594c..28f7e377 100644 --- a/src/main/java/ru/investbook/api/SecurityRestController.java +++ b/src/main/java/ru/investbook/api/SecurityRestController.java @@ -100,7 +100,7 @@ public ResponseEntity delete(@PathVariable("id") } @Override - protected Integer getId(Security object) { + public Integer getId(Security object) { return object.getId(); } diff --git a/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java b/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java index 8be6772e..b09d4dea 100644 --- a/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java @@ -144,7 +144,7 @@ public ResponseEntity delete(@PathVariable("id") } @Override - protected Integer getId(TransactionCashFlow object) { + public Integer getId(TransactionCashFlow object) { return object.getId(); } diff --git a/src/main/java/ru/investbook/api/TransactionRestController.java b/src/main/java/ru/investbook/api/TransactionRestController.java index fad619cd..cac5a36a 100644 --- a/src/main/java/ru/investbook/api/TransactionRestController.java +++ b/src/main/java/ru/investbook/api/TransactionRestController.java @@ -151,7 +151,7 @@ public ResponseEntity delete(@PathVariable("id") } @Override - protected Integer getId(Transaction object) { + public Integer getId(Transaction object) { return object.getId(); } From e71624c8531412c72b598349ef3125296fefa5ec Mon Sep 17 00:00:00 2001 From: vananiev Date: Sat, 27 Jul 2024 14:53:42 +0300 Subject: [PATCH 27/40] refactor InvestbookApiClient.saveWithoutUpdate() --- .../api/AbstractEntityRepositoryService.java | 16 +++-- .../api/AbstractRestController.java | 4 +- .../api/EntityRepositoryService.java | 17 +++-- .../parser/InvestbookApiClient.java | 65 +++++++++---------- .../AbstractEntityRepositoryServiceTest.java | 10 +-- 5 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java b/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java index 61652ae6..1d8362fc 100644 --- a/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java +++ b/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java @@ -73,12 +73,16 @@ public Page getPage(Pageable pageable) { .map(converter::fromEntity); } + /** + * @implNote Method performance is the same as {@link #createIfAbsent(Object)} for H2 2.2.224 and MariaDB 11.2 + */ @Override @SuppressWarnings("deprecation") public boolean insert(Pojo object) { if (entityManager instanceof Session hibernateSpecificSession) { try { Entity entity = converter.toEntity(object); + // Hibernate save() method does sql INSERT transactionTemplateRequiresNew.executeWithoutResult(_ -> hibernateSpecificSession.save(entity)); return true; } catch (Exception e) { @@ -88,21 +92,21 @@ public boolean insert(Pojo object) { log.error("Can't INSERT by optimized deprecated Hibernate method save(): {}", object, e); } } - Boolean result = transactionTemplateRequired.execute(_ -> create(object)); + Boolean result = transactionTemplateRequired.execute(_ -> createIfAbsent(object)); return Boolean.TRUE.equals(result); } @Override @Transactional - public boolean create(Pojo object) { - return createInternal(object) + public boolean createIfAbsent(Pojo object) { + return createIfAbsentInternal(object) .isPresent(); } @Override @Transactional - public Optional createAndGet(Pojo object) { - return createInternal(object) + public Optional createAndGetIfAbsent(Pojo object) { + return createIfAbsentInternal(object) .map(converter::fromEntity); } @@ -112,7 +116,7 @@ public Optional createAndGet(Pojo object) { * @return created entity if object is created or empty Optional otherwise * @implSpec Should be called in transaction */ - private Optional createInternal(Pojo object) { + private Optional createIfAbsentInternal(Pojo object) { ID id = getId(object); if (id != null && existsById(id)) { return Optional.empty(); diff --git a/src/main/java/ru/investbook/api/AbstractRestController.java b/src/main/java/ru/investbook/api/AbstractRestController.java index f5092568..6c25c9ec 100644 --- a/src/main/java/ru/investbook/api/AbstractRestController.java +++ b/src/main/java/ru/investbook/api/AbstractRestController.java @@ -69,7 +69,7 @@ public ResponseEntity get(ID id) { @Transactional protected ResponseEntity post(Pojo object) { try { - return createAndGet(object) + return createAndGetIfAbsent(object) .map(this::createResponseWithLocationHeader) .orElseGet(() -> ResponseEntity .status(HttpStatus.CONFLICT) @@ -100,7 +100,7 @@ public ResponseEntity put(ID id, Pojo object) { "запроса [" + objectId + "] не совпадают"); } Pojo objectWithId = nonNull(objectId) ? object : updateId(id, object); - return createAndGet(objectWithId) + return createAndGetIfAbsent(objectWithId) .map(this::createResponseWithLocationHeader) .orElseGet(() -> { createOrUpdate(objectWithId); diff --git a/src/main/java/ru/investbook/api/EntityRepositoryService.java b/src/main/java/ru/investbook/api/EntityRepositoryService.java index 6c51c47c..d0458af8 100644 --- a/src/main/java/ru/investbook/api/EntityRepositoryService.java +++ b/src/main/java/ru/investbook/api/EntityRepositoryService.java @@ -35,11 +35,10 @@ public interface EntityRepositoryService { /** * Creates a new object with direct INSERT into DB (without prior SELECT call) if possible, - * calls {@link #create(Object)} otherwise. + * calls {@link #createIfAbsent(Object)} otherwise. * * @return true if object is created, false if object with ID already exists - * @throws RuntimeException if an INSERT error occurs - * @implNote Method performance is the same as {@link #create(Object)} for H2 2.2.224 and MariaDB 11.2 + * @throws RuntimeException if object not exists and an INSERT error occurs */ boolean insert(Pojo object); @@ -48,21 +47,21 @@ public interface EntityRepositoryService { * Calls SELECT to check if object's ID exists in DB. * * @return true if object was created, false if object with ID already exists - * @throws RuntimeException if an INSERT error occurs + * @throws RuntimeException if object not exists and an INSERT error occurs * @see #insert(Object) */ - boolean create(Pojo object); + boolean createIfAbsent(Pojo object); /** * Creates new object, doesn't update. * Calls SELECT to check if object's ID exists in DB. - * Use faster {@link #create(Object)} method if saved object is not required + * Use faster {@link #createIfAbsent(Object)} method if saved object is not required * * @return created object or empty Optional if object with ID already exists - * @throws RuntimeException if an INSERT error occurs - * @see #create(Object) + * @throws RuntimeException if object not exists and an INSERT error occurs + * @see #createIfAbsent(Object) */ - Optional createAndGet(Pojo object); + Optional createAndGetIfAbsent(Pojo object); /** * Create new or update existing object in DB. diff --git a/src/main/java/ru/investbook/parser/InvestbookApiClient.java b/src/main/java/ru/investbook/parser/InvestbookApiClient.java index e0458bc9..0975f758 100644 --- a/src/main/java/ru/investbook/parser/InvestbookApiClient.java +++ b/src/main/java/ru/investbook/parser/InvestbookApiClient.java @@ -54,6 +54,7 @@ import java.util.List; import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Function; import static org.spacious_team.broker.pojo.CashFlowType.DERIVATIVE_PROFIT; @@ -78,17 +79,17 @@ public class InvestbookApiClient { private final ValidatorService validator; public boolean addPortfolio(Portfolio portfolio) { - return handlePost( + return saveWithoutUpdate( portfolio, - portfolioRestController::post, + portfolioRestController::createIfAbsent, "Не могу сохранить Портфель"); } public void addSecurity(Security security) { security = convertDerivativeSecurityId(security); - handlePost( + saveWithoutUpdate( security, - securityRestController::post, + securityRestController::createIfAbsent, "Не могу добавить ЦБ "); } @@ -101,7 +102,7 @@ private Security convertDerivativeSecurityId(Security security) { } public void addSecurityDescription(SecurityDescription securityDescription) { - handlePost( + saveWithoutUpdate( securityDescription, securityDescriptionRestController::post, "Не могу добавить метаинформацию о ЦБ "); @@ -136,23 +137,23 @@ private void addCashTransactionFlows(AbstractTransaction transaction, int transa } public boolean addTransaction(Transaction transaction) { - return handlePost( + return saveWithoutUpdate( transaction, - transactionRestController::post, + transactionRestController::createIfAbsent, "Не могу добавить транзакцию"); } public void addTransactionCashFlow(TransactionCashFlow transactionCashFlow) { - handlePost( + saveWithoutUpdate( transactionCashFlow, - transactionCashFlowRestController::post, + transactionCashFlowRestController::createIfAbsent, "Не могу добавить информацию о передвижении средств"); } public void addEventCashFlow(EventCashFlow eventCashFlow) { - handlePost( + saveWithoutUpdate( eventCashFlow, - eventCashFlowRestController::post, + eventCashFlowRestController::createIfAbsent, "Не могу добавить информацию о движении денежных средств"); } @@ -160,65 +161,61 @@ public void addSecurityEventCashFlow(SecurityEventCashFlow cf) { if (cf.getCount() == null && cf.getEventType() == DERIVATIVE_PROFIT) { cf = cf.toBuilder().count(0).build(); // count is optional for derivatives } - handlePost( + saveWithoutUpdate( cf, - securityEventCashFlowRestController::post, + securityEventCashFlowRestController::createIfAbsent, "Не могу добавить информацию о движении денежных средств"); } public void addPortfolioCash(PortfolioCash cash) { - handlePost( + saveWithoutUpdate( cash, - portfolioCashRestController::post, + portfolioCashRestController::createIfAbsent, "Не могу добавить информацию об остатках денежных средств портфеля"); } public void addPortfolioProperty(PortfolioProperty property) { - handlePost( + saveWithoutUpdate( property, - portfolioPropertyRestController::post, + portfolioPropertyRestController::createIfAbsent, "Не могу добавить информацию о свойствах портфеля"); } public void addSecurityQuote(SecurityQuote securityQuote) { - handlePost( + saveWithoutUpdate( securityQuote, - securityQuoteRestController::post, + securityQuoteRestController::createIfAbsent, "Не могу добавить информацию о котировке финансового инструмента"); } public void addForeignExchangeRate(ForeignExchangeRate exchangeRate) { - handlePost( + saveWithoutUpdate( exchangeRate, - foreignExchangeRateRestController::post, + foreignExchangeRateRestController::createIfAbsent, "Не могу добавить информацию о курсе валюты"); } /** - * @return true if new row was added, or it was already exists in DB, false - or error + * @return true - if object was created, or it was already exists in DB, + * false - if object not exists and create error was occurred */ - // todo replace RestController with EntityRepositoryService - private boolean handlePost(T object, Function> saver, String errorPrefix) { + private boolean saveWithoutUpdate(T object, Consumer persistFunction, String errorMsg) { try { validator.validate(object); - HttpStatusCode status = saver.apply(object).getStatusCode(); - if (!status.is2xxSuccessful() && status != HttpStatus.CONFLICT) { - log.warn(errorPrefix + " " + object); - return false; - } + persistFunction.accept(object); + return true; } catch (ConstraintViolationException e) { - log.warn("{} {}: {}", errorPrefix, object, e.getMessage()); + log.warn("{} {}: {}", errorMsg, object, e.getMessage()); return false; } catch (Exception e) { if (isUniqIndexViolationException(e)) { - log.debug("Дублирование информации: {} {}", errorPrefix, object); + log.debug("Дублирование информации: {} {}", errorMsg, object); log.trace("Дублирование вызвано исключением", e); - return true; // same as above status == HttpStatus.CONFLICT + return true; // object already exists } else { - log.warn("{} {}", errorPrefix, object, e); + log.warn("{} {}", errorMsg, object, e); return false; } } - return true; } } diff --git a/src/test/java/ru/investbook/api/AbstractEntityRepositoryServiceTest.java b/src/test/java/ru/investbook/api/AbstractEntityRepositoryServiceTest.java index d410acfb..2fa7cff2 100644 --- a/src/test/java/ru/investbook/api/AbstractEntityRepositoryServiceTest.java +++ b/src/test/java/ru/investbook/api/AbstractEntityRepositoryServiceTest.java @@ -49,11 +49,11 @@ void insert() { } @Test - void create() { - test("create()", service::create, false); - test("create()", service::create, false); - test("create()", service::create, true); - test("create()", service::create, true); + void createIfAbsent() { + test("createIfAbsent()", service::createIfAbsent, false); + test("createIfAbsent()", service::createIfAbsent, false); + test("createIfAbsent()", service::createIfAbsent, true); + test("createIfAbsent()", service::createIfAbsent, true); } void test(String name, Consumer consumer, boolean isIdNull) { From 402621b32099c1cda3584fb23579869169a1e273 Mon Sep 17 00:00:00 2001 From: vananiev Date: Sat, 31 Aug 2024 14:38:50 +0300 Subject: [PATCH 28/40] fix transaction controller POST location header value "/transactions/null" --- .../api/AbstractEntityRepositoryService.java | 65 +++++++++++++++++-- .../api/AbstractRestController.java | 16 +++-- .../java/ru/investbook/api/CreateResult.java | 33 ++++++++++ .../api/EntityRepositoryService.java | 11 ++++ .../repository/ConstraintAwareRepository.java | 41 ++++++++++++ .../repository/TransactionRepository.java | 17 ++++- 6 files changed, 170 insertions(+), 13 deletions(-) create mode 100644 src/main/java/ru/investbook/api/CreateResult.java create mode 100644 src/main/java/ru/investbook/repository/ConstraintAwareRepository.java diff --git a/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java b/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java index 1d8362fc..fc5580f5 100644 --- a/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java +++ b/src/main/java/ru/investbook/api/AbstractEntityRepositoryService.java @@ -31,6 +31,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionTemplate; import ru.investbook.converter.EntityConverter; +import ru.investbook.repository.ConstraintAwareRepository; import java.util.Optional; @@ -110,6 +111,11 @@ public Optional createAndGetIfAbsent(Pojo object) { .map(converter::fromEntity); } + @Override + public CreateResult createIfAbsentAndGet(Pojo object) { + return createIfAbsentAndGetInternal(object); + } + /** * Creates a new object (with SELECT check) * @@ -117,17 +123,64 @@ public Optional createAndGetIfAbsent(Pojo object) { * @implSpec Should be called in transaction */ private Optional createIfAbsentInternal(Pojo object) { - ID id = getId(object); - if (id != null && existsById(id)) { - return Optional.empty(); + Entity entity = null; + if (repository instanceof ConstraintAwareRepository caRepository) { + entity = converter.toEntity(object); + if (caRepository.exists(entity)) { + return Optional.empty(); + } + } else { + ID id = getId(object); + if (id != null && existsById(id)) { + return Optional.empty(); + } } - // Если работать не в транзакции, то следующая строка может повторно создать объект. - // Это возможно, если объект был создан другим потоком после проверки существования строки по ID. + + // Если работать не в транзакции, то следующие строки могут повторно создать объект. + // Это возможно, если объект был создан другим потоком после проверки существования строки. // Метод должен работать в транзакции. - Entity savedEntity = createOrUpdateInternal(object); + if (entity == null) { + entity = converter.toEntity(object); + } + Entity savedEntity = repository.save(entity); return Optional.of(savedEntity); } + /** + * Creates a new object (with SELECT check) + * + * @return created entity if object is created or existing object otherwise + * @implSpec Should be called in transaction + */ + private CreateResult createIfAbsentAndGetInternal(Pojo object) { + Entity entity = null; + Optional selectedEntity; + if (repository instanceof ConstraintAwareRepository caRepository) { + entity = converter.toEntity(object); + selectedEntity = caRepository.findBy(entity); + } else { + ID id = getId(object); + selectedEntity = Optional.ofNullable(id) + .flatMap(repository::findById); + } + if (selectedEntity.isPresent()) { + return selectedEntity + .map(converter::fromEntity) + .map(CreateResult::selected) + .orElseThrow(); + } + + // Если работать не в транзакции, то следующие строки могут повторно создать объект. + // Это возможно, если объект был создан другим потоком после проверки существования строки. + // Метод должен работать в транзакции. + if (entity == null) { + entity = converter.toEntity(object); + } + Entity savedEntity = repository.save(entity); + Pojo savedObject = converter.fromEntity(savedEntity); + return CreateResult.created(savedObject); + } + @Override @Transactional public void createOrUpdate(Pojo object) { diff --git a/src/main/java/ru/investbook/api/AbstractRestController.java b/src/main/java/ru/investbook/api/AbstractRestController.java index 6c25c9ec..ae71270e 100644 --- a/src/main/java/ru/investbook/api/AbstractRestController.java +++ b/src/main/java/ru/investbook/api/AbstractRestController.java @@ -69,12 +69,16 @@ public ResponseEntity get(ID id) { @Transactional protected ResponseEntity post(Pojo object) { try { - return createAndGetIfAbsent(object) - .map(this::createResponseWithLocationHeader) - .orElseGet(() -> ResponseEntity - .status(HttpStatus.CONFLICT) - .location(getLocationURI(object)) - .build()); + CreateResult result = createIfAbsentAndGet(object); + Pojo savedObject = result.object(); + if (result.created()) { + return createResponseWithLocationHeader(savedObject); + } else { + return ResponseEntity + .status(HttpStatus.CONFLICT) + .location(getLocationURI(savedObject)) + .build(); + } } catch (Exception e) { throw new InternalServerErrorException("Не могу создать объект", e); } diff --git a/src/main/java/ru/investbook/api/CreateResult.java b/src/main/java/ru/investbook/api/CreateResult.java new file mode 100644 index 00000000..e349c82a --- /dev/null +++ b/src/main/java/ru/investbook/api/CreateResult.java @@ -0,0 +1,33 @@ +/* + * InvestBook + * Copyright (C) 2024 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package ru.investbook.api; + +/** + * @param created true is new object was created, false if existing object is returned + */ +public record CreateResult(T object, boolean created) { + + public static CreateResult created(T object) { + return new CreateResult<>(object, true); + } + + public static CreateResult selected(T object) { + return new CreateResult<>(object, false); + } +} diff --git a/src/main/java/ru/investbook/api/EntityRepositoryService.java b/src/main/java/ru/investbook/api/EntityRepositoryService.java index d0458af8..1a802d5c 100644 --- a/src/main/java/ru/investbook/api/EntityRepositoryService.java +++ b/src/main/java/ru/investbook/api/EntityRepositoryService.java @@ -63,6 +63,17 @@ public interface EntityRepositoryService { */ Optional createAndGetIfAbsent(Pojo object); + /** + * Creates new object, doesn't update. + * Calls SELECT to check if object's ID exists in DB. + * Use faster {@link #createIfAbsent(Object)} method if saved object is not required + * + * @return created object or existing object without update if object with ID already exists + * @throws RuntimeException if object not exists and an INSERT error occurs + * @see #createIfAbsent(Object) + */ + CreateResult createIfAbsentAndGet(Pojo object); + /** * Create new or update existing object in DB. * Use instead of the slower method {@link #createOrUpdateAndGet(Object)} diff --git a/src/main/java/ru/investbook/repository/ConstraintAwareRepository.java b/src/main/java/ru/investbook/repository/ConstraintAwareRepository.java new file mode 100644 index 00000000..ae3ed9b8 --- /dev/null +++ b/src/main/java/ru/investbook/repository/ConstraintAwareRepository.java @@ -0,0 +1,41 @@ +/* + * InvestBook + * Copyright (C) 2024 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package ru.investbook.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +import java.util.Optional; + +/** + * JpaRepository that knows about UNIQUE KEYS + */ +@NoRepositoryBean +public interface ConstraintAwareRepository extends JpaRepository { + + /** + * Checks entity existence by UNIQUE KEY fields, other fields is ignored + */ + boolean exists(T probe); + + /** + * Selects entity by UNIQUE KEY fields, other fields is ignored + */ + Optional findBy(T probe); +} diff --git a/src/main/java/ru/investbook/repository/TransactionRepository.java b/src/main/java/ru/investbook/repository/TransactionRepository.java index 30528fc0..799e4aca 100644 --- a/src/main/java/ru/investbook/repository/TransactionRepository.java +++ b/src/main/java/ru/investbook/repository/TransactionRepository.java @@ -38,7 +38,8 @@ @Transactional(readOnly = true) public interface TransactionRepository extends JpaRepository, - JpaSpecificationExecutor { + JpaSpecificationExecutor, + ConstraintAwareRepository { Optional findFirstByOrderByTimestampAsc(); @@ -336,4 +337,18 @@ Collection findByPortfolioAndSecurityIdAndTimestampBetweenDep @Param("to") Instant toDate); int countByPortfolioIn(Set portfolio); + + @Override + default boolean exists(TransactionEntity probe) { + return countByIdOrPortfolioAndTradeId(probe.getId(), probe.getPortfolio(), probe.getTradeId()) > 0; + } + + long countByIdOrPortfolioAndTradeId(Integer id, String portfolio, String tradeId); + + @Override + default Optional findBy(TransactionEntity probe) { + return findByIdOrPortfolioAndTradeId(probe.getId(), probe.getPortfolio(), probe.getTradeId()); + } + + Optional findByIdOrPortfolioAndTradeId(Integer id, String portfolio, String tradeId); } From 709933ec5b5f41f6f40a3ddffbd50a459858ee94 Mon Sep 17 00:00:00 2001 From: vananiev Date: Sun, 1 Sep 2024 20:02:32 +0300 Subject: [PATCH 29/40] make POST response Location header optional --- .../investbook/api/AbstractRestController.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/ru/investbook/api/AbstractRestController.java b/src/main/java/ru/investbook/api/AbstractRestController.java index ae71270e..03d99d12 100644 --- a/src/main/java/ru/investbook/api/AbstractRestController.java +++ b/src/main/java/ru/investbook/api/AbstractRestController.java @@ -25,6 +25,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.util.UriUtils; import ru.investbook.converter.EntityConverter; @@ -57,7 +58,7 @@ public ResponseEntity get(ID id) { /** * Creates a new entity. - * If entity has ID and record with this ID already exists in DB, "409 Conflict" http status and Location header was returned. + * If entity has ID and record with this ID already exists in DB, "409 Conflict" http status and optional Location header was returned. * Otherwise, "201 Created" http status will be returned with Location header. * When creating new object, ID may be passed, but that ID only used when no {@link GeneratedValue} set * on Entity ID field or if {@link GeneratedValue#generator} set to {@link org.hibernate.generator.BeforeExecutionGenerator}, @@ -74,16 +75,23 @@ protected ResponseEntity post(Pojo object) { if (result.created()) { return createResponseWithLocationHeader(savedObject); } else { - return ResponseEntity - .status(HttpStatus.CONFLICT) - .location(getLocationURI(savedObject)) - .build(); + return createConflictResponse(savedObject); } } catch (Exception e) { throw new InternalServerErrorException("Не могу создать объект", e); } } + @NonNull + private ResponseEntity createConflictResponse(Pojo object) { + ResponseEntity.BodyBuilder response = ResponseEntity.status(HttpStatus.CONFLICT); + if (getId(object) != null) { + URI locationURI = getLocationURI(object); + response.location(locationURI); + } + return response.build(); + } + /** * Updates or creates a new entity. * In create case method returns "201 Created" http status and Location header. From 1cee892b37fe15801896bcb5d94b4ea33d105758 Mon Sep 17 00:00:00 2001 From: vananiev Date: Sun, 1 Sep 2024 20:09:47 +0300 Subject: [PATCH 30/40] improve uniq index violation check for h2 and mariadb --- .../repository/RepositoryHelper.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/investbook/repository/RepositoryHelper.java b/src/main/java/ru/investbook/repository/RepositoryHelper.java index 32b25029..7b58d4e5 100644 --- a/src/main/java/ru/investbook/repository/RepositoryHelper.java +++ b/src/main/java/ru/investbook/repository/RepositoryHelper.java @@ -20,15 +20,35 @@ import org.hibernate.exception.ConstraintViolationException; +import java.sql.SQLException; +import java.util.Objects; + public class RepositoryHelper { /** - * May return false positive result if NOT NULL column set by NULL + * May return false positive result if NOT NULL column set by NULL (or for other constraint violations) + * for not H2 or MariaDB RDBMS */ public static boolean isUniqIndexViolationException(Throwable t) { do { if (t instanceof ConstraintViolationException) { - return true; + // todo Не точное условие, нужно выбирать + Throwable cause = t.getCause(); + if (cause instanceof SQLException sqlException) { + int errorCode = sqlException.getErrorCode(); + String sqlState = sqlException.getSQLState(); + String packageName = cause.getClass().getPackageName(); + // https://www.h2database.com/javadoc/org/h2/api/ErrorCode.html#DUPLICATE_KEY_1 + if (errorCode == 23505 && Objects.equals(packageName, "org.h2.jdbc")) { + return true; // H2 + } else if (errorCode == 1062 && + Objects.equals(sqlState, "23000") && + Objects.equals(packageName, "java.sql")) { + // https://mariadb.com/kb/en/mariadb-error-code-reference/ + return true; // MariaDB + } + } + return true; // other databases } } while ((t = t.getCause()) != null); return false; From cfedfab1e827f68d5962e3354a45ccf322fb528c Mon Sep 17 00:00:00 2001 From: vananiev Date: Sun, 1 Sep 2024 20:13:10 +0300 Subject: [PATCH 31/40] replace [insert + select id] with singe select for duplicate transaction storing stage --- .../parser/InvestbookApiClient.java | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/main/java/ru/investbook/parser/InvestbookApiClient.java b/src/main/java/ru/investbook/parser/InvestbookApiClient.java index 0975f758..c02e8044 100644 --- a/src/main/java/ru/investbook/parser/InvestbookApiClient.java +++ b/src/main/java/ru/investbook/parser/InvestbookApiClient.java @@ -34,11 +34,8 @@ import org.spacious_team.broker.pojo.Transaction; import org.spacious_team.broker.pojo.TransactionCashFlow; import org.spacious_team.broker.report_parser.api.AbstractTransaction; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; +import ru.investbook.api.CreateResult; import ru.investbook.api.EventCashFlowRestController; import ru.investbook.api.ForeignExchangeRateRestController; import ru.investbook.api.PortfolioCashRestController; @@ -52,7 +49,6 @@ import ru.investbook.api.TransactionRestController; import ru.investbook.service.moex.MoexDerivativeCodeService; -import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @@ -109,23 +105,15 @@ public void addSecurityDescription(SecurityDescription securityDescription) { } public void addTransaction(AbstractTransaction transaction) { - boolean isAdded = addTransaction(transaction.getTransaction()); - if (isAdded) { - Optional.ofNullable(transaction.getId()) - .or(() -> getSavedTransactionId(transaction)) - .ifPresentOrElse( - transactionId -> addCashTransactionFlows(transaction, transactionId), - () -> log.warn("Не могу добавить транзакцию в БД, " + - "не задан внутренний идентификатор записи: {}", transaction)); - } - } - - private Optional getSavedTransactionId(AbstractTransaction transaction) { - // todo replace RestController with EntityRepositoryService - return Optional.of(transactionRestController.get(transaction.getPortfolio(), transaction.getTradeId(), Pageable.unpaged()).getContent()) - .filter(result -> result.size() == 1) - .map(List::getFirst) - .map(Transaction::getId); + saveWithoutUpdateAndGet( + transaction.getTransaction(), + transactionRestController::createIfAbsentAndGet, + "Не могу добавить транзакцию") + .map(Transaction::getId) + .ifPresentOrElse( + transactionId -> addCashTransactionFlows(transaction, transactionId), + () -> log.warn("Не могу добавить транзакцию в БД, " + + "не задан внутренний идентификатор записи: {}", transaction)); } private void addCashTransactionFlows(AbstractTransaction transaction, int transactionId) { @@ -136,8 +124,8 @@ private void addCashTransactionFlows(AbstractTransaction transaction, int transa .forEach(this::addTransactionCashFlow); } - public boolean addTransaction(Transaction transaction) { - return saveWithoutUpdate( + public void addTransaction(Transaction transaction) { + saveWithoutUpdate( transaction, transactionRestController::createIfAbsent, "Не могу добавить транзакцию"); @@ -204,7 +192,7 @@ private boolean saveWithoutUpdate(T object, Consumer persistFunction, Str validator.validate(object); persistFunction.accept(object); return true; - } catch (ConstraintViolationException e) { + } catch (ConstraintViolationException e) { // jakarta.validation, not SQL constraint log.warn("{} {}: {}", errorMsg, object, e.getMessage()); return false; } catch (Exception e) { @@ -218,4 +206,20 @@ private boolean saveWithoutUpdate(T object, Consumer persistFunction, Str } } } + + /** + * @return true - if object was created, or it was already exists in DB, + * false - if object not exists and create error was occurred + */ + private Optional saveWithoutUpdateAndGet(T object, Function> persistFunction, + @SuppressWarnings("SameParameterValue") String errorMsg) { + try { + validator.validate(object); + CreateResult result = persistFunction.apply(object); + return Optional.of(result.object()); + } catch (Exception e) { // jakarta.validation, not SQL constraint + log.warn("{} {}: {}", errorMsg, object, e.getMessage()); + return Optional.empty(); + } + } } From 77d5353d01d4dcd466d06acb1a98a1ad4d3eb20d Mon Sep 17 00:00:00 2001 From: vananiev Date: Sun, 1 Sep 2024 21:07:32 +0300 Subject: [PATCH 32/40] replace [insert + select id] with singe select for duplicate transaction storing stage --- src/main/java/ru/investbook/parser/InvestbookApiClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/investbook/parser/InvestbookApiClient.java b/src/main/java/ru/investbook/parser/InvestbookApiClient.java index c02e8044..2034caa5 100644 --- a/src/main/java/ru/investbook/parser/InvestbookApiClient.java +++ b/src/main/java/ru/investbook/parser/InvestbookApiClient.java @@ -217,7 +217,8 @@ private Optional saveWithoutUpdateAndGet(T object, Function result = persistFunction.apply(object); return Optional.of(result.object()); - } catch (Exception e) { // jakarta.validation, not SQL constraint + } catch (Exception e) { + // should not be thrown for duplicate log.warn("{} {}: {}", errorMsg, object, e.getMessage()); return Optional.empty(); } From 3b257bededc0ad8a2813559030d900a10510d830 Mon Sep 17 00:00:00 2001 From: vananiev Date: Sun, 1 Sep 2024 22:34:57 +0300 Subject: [PATCH 33/40] fix lzy loading by optional=false property https://stackoverflow.com/questions/17987638/hibernate-one-to-one-lazy-loading-optional-false --- .../ru/investbook/entity/EventCashFlowEntity.java | 8 ++++---- .../investbook/entity/PortfolioPropertyEntity.java | 4 ++-- .../entity/SecurityEventCashFlowEntity.java | 12 ++++++------ .../ru/investbook/entity/SecurityQuoteEntity.java | 4 ++-- .../investbook/entity/TransactionCashFlowEntity.java | 4 ++-- .../java/ru/investbook/entity/TransactionEntity.java | 9 +++++---- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/main/java/ru/investbook/entity/EventCashFlowEntity.java b/src/main/java/ru/investbook/entity/EventCashFlowEntity.java index d3b51737..ae762c1c 100644 --- a/src/main/java/ru/investbook/entity/EventCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/EventCashFlowEntity.java @@ -44,8 +44,8 @@ public class EventCashFlowEntity { @Column(name = "id") private Integer id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "portfolio", referencedColumnName = "id") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "portfolio", referencedColumnName = "id", nullable = false) @JsonIgnoreProperties({"hibernateLazyInitializer"}) private PortfolioEntity portfolio; @@ -53,8 +53,8 @@ public class EventCashFlowEntity { @Column(name = "timestamp") private Instant timestamp; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "type", referencedColumnName = "id") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "type", referencedColumnName = "id", nullable = false) @JsonIgnoreProperties({"hibernateLazyInitializer"}) private CashFlowTypeEntity cashFlowType; diff --git a/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java b/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java index 6abf1609..f2cd6aad 100644 --- a/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java +++ b/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java @@ -41,8 +41,8 @@ public class PortfolioPropertyEntity { @Column(name = "id") private Integer id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "portfolio", referencedColumnName = "id") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "portfolio", referencedColumnName = "id", nullable = false) @JsonIgnoreProperties({"hibernateLazyInitializer"}) private PortfolioEntity portfolio; diff --git a/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java b/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java index c15df622..fa581868 100644 --- a/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java @@ -44,8 +44,8 @@ public class SecurityEventCashFlowEntity { @Column(name = "id") private Integer id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "portfolio", referencedColumnName = "id") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "portfolio", referencedColumnName = "id", nullable = false) @JsonIgnoreProperties({"hibernateLazyInitializer"}) private PortfolioEntity portfolio; @@ -53,8 +53,8 @@ public class SecurityEventCashFlowEntity { @Column(name = "timestamp") private Instant timestamp; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "security", referencedColumnName = "id") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "security", referencedColumnName = "id", nullable = false) @JsonIgnoreProperties({"hibernateLazyInitializer"}) private SecurityEntity security; @@ -62,8 +62,8 @@ public class SecurityEventCashFlowEntity { @Column(name = "count") private Integer count; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "type", referencedColumnName = "id") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "type", referencedColumnName = "id", nullable = false) @JsonIgnoreProperties({"hibernateLazyInitializer"}) private CashFlowTypeEntity cashFlowType; diff --git a/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java b/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java index b3825738..d63a76a9 100644 --- a/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java +++ b/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java @@ -42,8 +42,8 @@ public class SecurityQuoteEntity { @Column(name = "id") private Integer id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "security", referencedColumnName = "id") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "security", referencedColumnName = "id", nullable = false) @JsonIgnoreProperties({"hibernateLazyInitializer"}) private SecurityEntity security; diff --git a/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java b/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java index 1aa8e63d..3e931de0 100644 --- a/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java @@ -45,7 +45,7 @@ public class TransactionCashFlowEntity { @Column(name = "transaction_id") private int transactionId; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "type", referencedColumnName = "id", nullable = false) private CashFlowTypeEntity cashFlowType; @@ -61,7 +61,7 @@ public class TransactionCashFlowEntity { /* Nowadays not used, commented due to perf issue - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumns({ @JoinColumn(name = "transaction_id", referencedColumnName = "id", insertable = false, updatable = false), @JoinColumn(name = "portfolio", referencedColumnName = "portfolio", insertable = false, updatable = false) diff --git a/src/main/java/ru/investbook/entity/TransactionEntity.java b/src/main/java/ru/investbook/entity/TransactionEntity.java index f5ea4f83..92c40b3f 100644 --- a/src/main/java/ru/investbook/entity/TransactionEntity.java +++ b/src/main/java/ru/investbook/entity/TransactionEntity.java @@ -53,13 +53,14 @@ public class TransactionEntity { @Column(name = "portfolio") private String portfolio; -// @ManyToOne(fetch = FetchType.LAZY) -// @JoinColumn(name = "portfolio", referencedColumnName = "id") +// @ManyToOne(fetch = FetchType.LAZY, optional = false) +// @JoinColumn(name = "portfolio", referencedColumnName = "id", nullable = false) // @JsonIgnoreProperties({"hibernateLazyInitializer"}) // private PortfolioEntity portfolio; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "security", referencedColumnName = "id") + // https://stackoverflow.com/questions/17987638/hibernate-one-to-one-lazy-loading-optional-false + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "security", referencedColumnName = "id", nullable = false) @JsonIgnoreProperties({"hibernateLazyInitializer"}) private SecurityEntity security; From c997a411a670ec2fe68904a5c1df46059f16081a Mon Sep 17 00:00:00 2001 From: vananiev Date: Sun, 1 Sep 2024 22:47:36 +0300 Subject: [PATCH 34/40] do not do extra select --- .../ru/investbook/converter/EventCashFlowConverter.java | 6 ++---- .../investbook/converter/PortfolioPropertyConverter.java | 3 +-- .../converter/SecurityDescriptionConverter.java | 3 +-- .../converter/SecurityEventCashFlowConverter.java | 9 +++------ .../ru/investbook/converter/SecurityQuoteConverter.java | 4 +--- .../converter/TransactionCashFlowConverter.java | 6 +----- .../ru/investbook/converter/TransactionConverter.java | 7 +------ 7 files changed, 10 insertions(+), 28 deletions(-) diff --git a/src/main/java/ru/investbook/converter/EventCashFlowConverter.java b/src/main/java/ru/investbook/converter/EventCashFlowConverter.java index 7f0a0d39..b0639a67 100644 --- a/src/main/java/ru/investbook/converter/EventCashFlowConverter.java +++ b/src/main/java/ru/investbook/converter/EventCashFlowConverter.java @@ -36,10 +36,8 @@ public class EventCashFlowConverter implements EntityConverter new IllegalArgumentException("В справочнике не найден брокерский счет: " + eventCashFlow.getPortfolio())); - CashFlowTypeEntity cashFlowTypeEntity = cashFlowTypeRepository.findById(eventCashFlow.getEventType().getId()) - .orElseThrow(() -> new IllegalArgumentException("В справочнике не найдено событие с типом: " + eventCashFlow.getEventType().getId())); + PortfolioEntity portfolioEntity = portfolioRepository.getReferenceById(eventCashFlow.getPortfolio()); + CashFlowTypeEntity cashFlowTypeEntity = cashFlowTypeRepository.getReferenceById(eventCashFlow.getEventType().getId()); EventCashFlowEntity entity = new EventCashFlowEntity(); entity.setId(eventCashFlow.getId()); diff --git a/src/main/java/ru/investbook/converter/PortfolioPropertyConverter.java b/src/main/java/ru/investbook/converter/PortfolioPropertyConverter.java index 6cb85e4c..8ab06baa 100644 --- a/src/main/java/ru/investbook/converter/PortfolioPropertyConverter.java +++ b/src/main/java/ru/investbook/converter/PortfolioPropertyConverter.java @@ -33,8 +33,7 @@ public class PortfolioPropertyConverter implements EntityConverter new IllegalArgumentException("В справочнике не найден брокерский счет: " + property.getPortfolio())); + PortfolioEntity portfolioEntity = portfolioRepository.getReferenceById(property.getPortfolio()); PortfolioPropertyEntity entity = new PortfolioPropertyEntity(); entity.setId(property.getId()); diff --git a/src/main/java/ru/investbook/converter/SecurityDescriptionConverter.java b/src/main/java/ru/investbook/converter/SecurityDescriptionConverter.java index 81b76159..e660e6e8 100644 --- a/src/main/java/ru/investbook/converter/SecurityDescriptionConverter.java +++ b/src/main/java/ru/investbook/converter/SecurityDescriptionConverter.java @@ -35,8 +35,7 @@ public class SecurityDescriptionConverter implements EntityConverter new IllegalArgumentException("Эмитент c идентификатором не найден: " + security.getIssuer())); + issuerEntity = issuerRepository.getReferenceById(security.getIssuer()); } SecurityDescriptionEntity entity = new SecurityDescriptionEntity(); diff --git a/src/main/java/ru/investbook/converter/SecurityEventCashFlowConverter.java b/src/main/java/ru/investbook/converter/SecurityEventCashFlowConverter.java index 639dcf9a..6a75f29d 100644 --- a/src/main/java/ru/investbook/converter/SecurityEventCashFlowConverter.java +++ b/src/main/java/ru/investbook/converter/SecurityEventCashFlowConverter.java @@ -39,12 +39,9 @@ public class SecurityEventCashFlowConverter implements EntityConverter new IllegalArgumentException("Ценная бумага с заданным ID не найдена: " + eventCashFlow.getSecurity())); - PortfolioEntity portfolioEntity = portfolioRepository.findById(eventCashFlow.getPortfolio()) - .orElseThrow(() -> new IllegalArgumentException("В справочнике не найден брокерский счет: " + eventCashFlow.getPortfolio())); - CashFlowTypeEntity cashFlowTypeEntity = cashFlowTypeRepository.findById(eventCashFlow.getEventType().getId()) - .orElseThrow(() -> new IllegalArgumentException("В справочнике не найдено событие с типом: " + eventCashFlow.getEventType().getId())); + SecurityEntity securityEntity = securityRepository.getReferenceById(eventCashFlow.getSecurity()); + PortfolioEntity portfolioEntity = portfolioRepository.getReferenceById(eventCashFlow.getPortfolio()); + CashFlowTypeEntity cashFlowTypeEntity = cashFlowTypeRepository.getReferenceById(eventCashFlow.getEventType().getId()); SecurityEventCashFlowEntity entity = new SecurityEventCashFlowEntity(); entity.setId(eventCashFlow.getId()); diff --git a/src/main/java/ru/investbook/converter/SecurityQuoteConverter.java b/src/main/java/ru/investbook/converter/SecurityQuoteConverter.java index 8bac03be..55753fe5 100644 --- a/src/main/java/ru/investbook/converter/SecurityQuoteConverter.java +++ b/src/main/java/ru/investbook/converter/SecurityQuoteConverter.java @@ -32,9 +32,7 @@ public class SecurityQuoteConverter implements EntityConverter new IllegalArgumentException("Ценная бумага с заданным ISIN не найдена: " + quote.getSecurity())); - + SecurityEntity securityEntity = securityRepository.getReferenceById(quote.getSecurity()); SecurityQuoteEntity entity = new SecurityQuoteEntity(); entity.setId(quote.getId()); diff --git a/src/main/java/ru/investbook/converter/TransactionCashFlowConverter.java b/src/main/java/ru/investbook/converter/TransactionCashFlowConverter.java index 799088e3..65ed1e09 100644 --- a/src/main/java/ru/investbook/converter/TransactionCashFlowConverter.java +++ b/src/main/java/ru/investbook/converter/TransactionCashFlowConverter.java @@ -35,11 +35,7 @@ public class TransactionCashFlowConverter implements EntityConverter new IllegalArgumentException("В справочнике не найдено событие с типом: " + cash.getEventType().getId())); + CashFlowTypeEntity cashFlowTypeEntity = cashFlowTypeRepository.getReferenceById(cash.getEventType().getId()); TransactionCashFlowEntity entity = new TransactionCashFlowEntity(); entity.setId(cash.getId()); diff --git a/src/main/java/ru/investbook/converter/TransactionConverter.java b/src/main/java/ru/investbook/converter/TransactionConverter.java index 86f34cd5..e502c906 100644 --- a/src/main/java/ru/investbook/converter/TransactionConverter.java +++ b/src/main/java/ru/investbook/converter/TransactionConverter.java @@ -23,21 +23,16 @@ import org.springframework.stereotype.Component; import ru.investbook.entity.SecurityEntity; import ru.investbook.entity.TransactionEntity; -import ru.investbook.repository.PortfolioRepository; import ru.investbook.repository.SecurityRepository; @Component @RequiredArgsConstructor public class TransactionConverter implements EntityConverter { - private final PortfolioRepository portfolioRepository; private final SecurityRepository securityRepository; @Override public TransactionEntity toEntity(Transaction transaction) { - portfolioRepository.findById(transaction.getPortfolio()) - .orElseThrow(() -> new IllegalArgumentException("В справочнике не найден брокерский счет: " + transaction.getPortfolio())); - SecurityEntity securityEntity = securityRepository.findById(transaction.getSecurity()) - .orElseThrow(() -> new IllegalArgumentException("Ценная бумага с заданным ID не найдена: " + transaction.getSecurity())); + SecurityEntity securityEntity = securityRepository.getReferenceById(transaction.getSecurity()); TransactionEntity entity = new TransactionEntity(); entity.setId(transaction.getId()); From 478ef69a9d7f457622e786b9bca81a18418a2095 Mon Sep 17 00:00:00 2001 From: vananiev Date: Sun, 1 Sep 2024 23:54:48 +0300 Subject: [PATCH 35/40] disable mariadb duplicate key errors --- src/main/resources/application-dev.properties | 1 + src/main/resources/application-mariadb.properties | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index f901f26e..e3f3269d 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -22,6 +22,7 @@ #spring.datasource.username = root #spring.datasource.password = 123456 #spring.datasource.driver-class-name=org.mariadb.jdbc.Driver +#logging.level.org.mariadb.jdbc.message.server.ErrorPacket=ERROR # H2 spring.datasource.url=jdbc:h2:file:~/investbook3-test;mode=mysql;non_keywords=value diff --git a/src/main/resources/application-mariadb.properties b/src/main/resources/application-mariadb.properties index b5b6be7a..682d3d9b 100644 --- a/src/main/resources/application-mariadb.properties +++ b/src/main/resources/application-mariadb.properties @@ -2,4 +2,5 @@ spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MariaDBDialect spring.datasource.username=root spring.datasource.password=123456 -spring.datasource.driver-class-name=org.mariadb.jdbc.Driver \ No newline at end of file +spring.datasource.driver-class-name=org.mariadb.jdbc.Driver +logging.level.org.mariadb.jdbc.message.server.ErrorPacket=ERROR From 0b60c4587f3c58dc181811ee94dcda8c4d1baf88 Mon Sep 17 00:00:00 2001 From: vananiev Date: Mon, 2 Sep 2024 11:22:56 +0300 Subject: [PATCH 36/40] add jpa entity nullable = false fields attribute --- .../java/ru/investbook/entity/CashFlowTypeEntity.java | 4 ++-- .../java/ru/investbook/entity/EventCashFlowEntity.java | 4 ++-- .../ru/investbook/entity/ForeignExchangeRateEntity.java | 2 +- .../ru/investbook/entity/ForeignExchangeRateEntityPk.java | 4 ++-- src/main/java/ru/investbook/entity/PortfolioEntity.java | 4 ++-- .../ru/investbook/entity/PortfolioPropertyEntity.java | 6 +++--- .../ru/investbook/entity/SecurityDescriptionEntity.java | 2 +- .../ru/investbook/entity/SecurityEventCashFlowEntity.java | 8 ++++---- .../java/ru/investbook/entity/SecurityQuoteEntity.java | 4 ++-- .../java/ru/investbook/entity/StockMarketIndexEntity.java | 1 + .../ru/investbook/entity/TransactionCashFlowEntity.java | 6 +++--- src/main/java/ru/investbook/entity/TransactionEntity.java | 8 ++++---- 12 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/main/java/ru/investbook/entity/CashFlowTypeEntity.java b/src/main/java/ru/investbook/entity/CashFlowTypeEntity.java index 61792273..7cc54871 100644 --- a/src/main/java/ru/investbook/entity/CashFlowTypeEntity.java +++ b/src/main/java/ru/investbook/entity/CashFlowTypeEntity.java @@ -30,10 +30,10 @@ @Data public class CashFlowTypeEntity { @Id - @Column(name = "id") + @Column(name = "id", nullable = false) private int id; @Basic - @Column(name = "name") + @Column(name = "name", nullable = false) private String name; } diff --git a/src/main/java/ru/investbook/entity/EventCashFlowEntity.java b/src/main/java/ru/investbook/entity/EventCashFlowEntity.java index ae762c1c..5301285d 100644 --- a/src/main/java/ru/investbook/entity/EventCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/EventCashFlowEntity.java @@ -59,11 +59,11 @@ public class EventCashFlowEntity { private CashFlowTypeEntity cashFlowType; @Basic - @Column(name = "value") + @Column(name = "value", nullable = false) private BigDecimal value; @Basic - @Column(name = "currency") + @Column(name = "currency", nullable = false) private String currency = "RUR"; @Basic diff --git a/src/main/java/ru/investbook/entity/ForeignExchangeRateEntity.java b/src/main/java/ru/investbook/entity/ForeignExchangeRateEntity.java index 96989e7e..7949cfd8 100644 --- a/src/main/java/ru/investbook/entity/ForeignExchangeRateEntity.java +++ b/src/main/java/ru/investbook/entity/ForeignExchangeRateEntity.java @@ -36,6 +36,6 @@ public class ForeignExchangeRateEntity { private ForeignExchangeRateEntityPk pk; @Basic - @Column(name = "rate") + @Column(name = "rate", nullable = false) private BigDecimal rate; } diff --git a/src/main/java/ru/investbook/entity/ForeignExchangeRateEntityPk.java b/src/main/java/ru/investbook/entity/ForeignExchangeRateEntityPk.java index f1402bae..6057033d 100644 --- a/src/main/java/ru/investbook/entity/ForeignExchangeRateEntityPk.java +++ b/src/main/java/ru/investbook/entity/ForeignExchangeRateEntityPk.java @@ -32,10 +32,10 @@ @Data public class ForeignExchangeRateEntityPk implements Serializable { - @Column(name = "date") + @Column(name = "date", nullable = false) private LocalDate date; @Basic - @Column(name = "currency_pair") + @Column(name = "currency_pair", nullable = false) private String currencyPair; } diff --git a/src/main/java/ru/investbook/entity/PortfolioEntity.java b/src/main/java/ru/investbook/entity/PortfolioEntity.java index 30c01b40..e6b1d91e 100644 --- a/src/main/java/ru/investbook/entity/PortfolioEntity.java +++ b/src/main/java/ru/investbook/entity/PortfolioEntity.java @@ -33,10 +33,10 @@ public class PortfolioEntity { @Id - @Column(name = "id") + @Column(name = "id", nullable = false) private String id; @Basic - @Column(name = "enabled") + @Column(name = "enabled", nullable = false) private boolean enabled; } diff --git a/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java b/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java index f2cd6aad..1e94be15 100644 --- a/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java +++ b/src/main/java/ru/investbook/entity/PortfolioPropertyEntity.java @@ -47,14 +47,14 @@ public class PortfolioPropertyEntity { private PortfolioEntity portfolio; @Basic - @Column(name = "timestamp") + @Column(name = "timestamp", nullable = false) private Instant timestamp; @Basic - @Column(name = "property") + @Column(name = "property", nullable = false) private String property; @Basic - @Column(name = "value") + @Column(name = "value", nullable = false) private String value; } diff --git a/src/main/java/ru/investbook/entity/SecurityDescriptionEntity.java b/src/main/java/ru/investbook/entity/SecurityDescriptionEntity.java index a0638fdf..4fbd73dd 100644 --- a/src/main/java/ru/investbook/entity/SecurityDescriptionEntity.java +++ b/src/main/java/ru/investbook/entity/SecurityDescriptionEntity.java @@ -35,7 +35,7 @@ @EqualsAndHashCode(of = "security") public class SecurityDescriptionEntity { @Id - @Column(name = "security") + @Column(name = "security", nullable = false) private int security; @Column(name = "sector") diff --git a/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java b/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java index fa581868..63505f7d 100644 --- a/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/SecurityEventCashFlowEntity.java @@ -50,7 +50,7 @@ public class SecurityEventCashFlowEntity { private PortfolioEntity portfolio; @Basic - @Column(name = "timestamp") + @Column(name = "timestamp", nullable = false) private Instant timestamp; @ManyToOne(fetch = FetchType.LAZY, optional = false) @@ -59,7 +59,7 @@ public class SecurityEventCashFlowEntity { private SecurityEntity security; @Basic - @Column(name = "count") + @Column(name = "count", nullable = false) private Integer count; @ManyToOne(fetch = FetchType.LAZY, optional = false) @@ -68,10 +68,10 @@ public class SecurityEventCashFlowEntity { private CashFlowTypeEntity cashFlowType; @Basic - @Column(name = "value") + @Column(name = "value", nullable = false) private BigDecimal value; @Basic - @Column(name = "currency") + @Column(name = "currency", nullable = false) private String currency = "RUR"; } diff --git a/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java b/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java index d63a76a9..1e4038e3 100644 --- a/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java +++ b/src/main/java/ru/investbook/entity/SecurityQuoteEntity.java @@ -48,11 +48,11 @@ public class SecurityQuoteEntity { private SecurityEntity security; @Basic - @Column(name = "timestamp") + @Column(name = "timestamp", nullable = false) private Instant timestamp; @Basic - @Column(name = "quote") + @Column(name = "quote", nullable = false) private BigDecimal quote; @Basic diff --git a/src/main/java/ru/investbook/entity/StockMarketIndexEntity.java b/src/main/java/ru/investbook/entity/StockMarketIndexEntity.java index afff6308..c8d08143 100644 --- a/src/main/java/ru/investbook/entity/StockMarketIndexEntity.java +++ b/src/main/java/ru/investbook/entity/StockMarketIndexEntity.java @@ -41,6 +41,7 @@ public class StockMarketIndexEntity { @Id + @Column(name = "date", nullable = false) private LocalDate date; @Basic diff --git a/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java b/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java index 3e931de0..73639849 100644 --- a/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java +++ b/src/main/java/ru/investbook/entity/TransactionCashFlowEntity.java @@ -42,7 +42,7 @@ public class TransactionCashFlowEntity { private Integer id; @Basic(optional = false) - @Column(name = "transaction_id") + @Column(name = "transaction_id", nullable = false) private int transactionId; @ManyToOne(fetch = FetchType.LAZY, optional = false) @@ -50,11 +50,11 @@ public class TransactionCashFlowEntity { private CashFlowTypeEntity cashFlowType; @Basic(optional = false) - @Column(name = "value") + @Column(name = "value", nullable = false) private BigDecimal value; @Basic(optional = false) - @Column(name = "currency") + @Column(name = "currency", nullable = false) private String currency = "RUR"; diff --git a/src/main/java/ru/investbook/entity/TransactionEntity.java b/src/main/java/ru/investbook/entity/TransactionEntity.java index 92c40b3f..c2f54c88 100644 --- a/src/main/java/ru/investbook/entity/TransactionEntity.java +++ b/src/main/java/ru/investbook/entity/TransactionEntity.java @@ -46,11 +46,11 @@ public class TransactionEntity { private Integer id; @Basic(optional = false) - @Column(name = "trade_id") + @Column(name = "trade_id", nullable = false) private String tradeId; @Basic(optional = false) - @Column(name = "portfolio") + @Column(name = "portfolio", nullable = false) private String portfolio; // @ManyToOne(fetch = FetchType.LAZY, optional = false) @@ -65,11 +65,11 @@ public class TransactionEntity { private SecurityEntity security; @Basic(optional = false) - @Column(name = "timestamp") + @Column(name = "timestamp", nullable = false) private Instant timestamp; @Basic(optional = false) - @Column(name = "count") + @Column(name = "count", nullable = false) private int count; /* From 96c907d6362e566c1f6e2ed90289091a91f5b705 Mon Sep 17 00:00:00 2001 From: vananiev Date: Mon, 22 Jul 2024 01:20:08 +0300 Subject: [PATCH 37/40] add openapi response codes --- pom.xml | 2 +- .../api/CashFlowTypeRestController.java | 18 +++++-- .../api/EventCashFlowRestController.java | 31 +++++++++--- .../ForeignExchangeRateRestController.java | 47 ++++++++++++++----- .../investbook/api/IssuerRestController.java | 33 ++++++++++--- .../api/PortfolioCashRestController.java | 31 +++++++++--- .../api/PortfolioPropertyRestController.java | 36 +++++++++++--- .../api/PortfolioRestController.java | 31 +++++++++--- .../SecurityDescriptionRestController.java | 37 +++++++++++---- .../SecurityEventCashFlowRestController.java | 34 ++++++++++---- .../api/SecurityQuoteRestController.java | 33 ++++++++++--- .../api/SecurityRestController.java | 37 +++++++++++---- .../TransactionCashFlowRestController.java | 36 +++++++++++--- .../api/TransactionRestController.java | 36 +++++++++++--- .../resources/application-core.properties | 3 +- 15 files changed, 346 insertions(+), 99 deletions(-) diff --git a/pom.xml b/pom.xml index 748f2853..12f498fe 100644 --- a/pom.xml +++ b/pom.xml @@ -189,7 +189,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.1.0 + 2.6.0 com.sun.mail diff --git a/src/main/java/ru/investbook/api/CashFlowTypeRestController.java b/src/main/java/ru/investbook/api/CashFlowTypeRestController.java index e454cc63..f70db7c6 100644 --- a/src/main/java/ru/investbook/api/CashFlowTypeRestController.java +++ b/src/main/java/ru/investbook/api/CashFlowTypeRestController.java @@ -20,6 +20,10 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.spacious_team.broker.pojo.CashFlowType; @@ -32,7 +36,6 @@ import ru.investbook.repository.CashFlowTypeRepository; import java.util.Optional; -import java.util.stream.Collectors; @RestController @RequiredArgsConstructor @@ -44,16 +47,23 @@ public class CashFlowTypeRestController { private final CashFlowTypeConverter cashFlowTypeConverter; @GetMapping - @Operation(summary = "Отобразить все") + @Operation(summary = "Отобразить все", + responses = { + @ApiResponse(responseCode = "200", content = @Content( + array = @ArraySchema(schema = @Schema(implementation = CashFlowType.class)))), + @ApiResponse(responseCode = "500", content = @Content)}) public Iterable getCashFlowType() { return cashFlowTypeRepository.findAll() .stream() .map(cashFlowTypeConverter::fromEntity) - .collect(Collectors.toList()); + .toList(); } @GetMapping("{id}") - @Operation(summary = "Отобразить по идентификатору") + @Operation(summary = "Отобразить по идентификатору", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity getCashFlowType(@PathVariable("id") @Parameter(description = "Идентификатор типа") Integer id) { diff --git a/src/main/java/ru/investbook/api/EventCashFlowRestController.java b/src/main/java/ru/investbook/api/EventCashFlowRestController.java index 236dbbae..f8477e66 100644 --- a/src/main/java/ru/investbook/api/EventCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/EventCashFlowRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.spacious_team.broker.pojo.EventCashFlow; @@ -39,6 +42,8 @@ import ru.investbook.converter.EntityConverter; import ru.investbook.entity.EventCashFlowEntity; +import static org.springframework.http.HttpHeaders.LOCATION; + @RestController @Tag(name = "Движения ДС по счету", description = """ Ввод, вывод ДС, налоги, комиссии, а также дивиденды, купоны, амортизации по бумагам другого счета @@ -54,7 +59,9 @@ public EventCashFlowRestController(JpaRepository r @Override @GetMapping @PageableAsQueryParam - @Operation(summary = "Отобразить все", description = "Отображает все выплаты по всем счетам") + @Operation(summary = "Отобразить все", description = "Отображает все выплаты по всем счетам", responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public Page get(@Parameter(hidden = true) Pageable pageable) { return super.get(pageable); @@ -62,7 +69,9 @@ public Page get(@Parameter(hidden = true) @Override @GetMapping("{id}") - @Operation(summary = "Отобразить одну", description = "Отобразить выплату по ее номеру") + @Operation(summary = "Отобразить одну", description = "Отобразить выплату по ее номеру", responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity get(@PathVariable("id") @Parameter(description = "Номер события") Integer id) { @@ -71,14 +80,20 @@ public ResponseEntity get(@PathVariable("id") @Override @PostMapping - @Operation(summary = "Добавить", description = "Сохранить информацию в БД") + @Operation(summary = "Добавить", description = "Сохранить информацию", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody EventCashFlow event) { return super.post(event); } @Override @PutMapping("{id}") - @Operation(summary = "Изменить", description = "Модифицировать информацию в БД") + @Operation(summary = "Обновить", description = "Модифицировать информацию", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("id") @Parameter(description = "Номер события") Integer id, @@ -90,10 +105,12 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") - @Operation(summary = "Удалить", description = "Удалить информацию из БД") + @Operation(summary = "Удалить", description = "Удалить информацию из БД", responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("id") - @Parameter(description = "Номер события") - Integer id) { + @Parameter(description = "Номер события") + Integer id) { return super.delete(id); } diff --git a/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java b/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java index 8e962a79..4b5d371c 100644 --- a/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java +++ b/src/main/java/ru/investbook/api/ForeignExchangeRateRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.SneakyThrows; @@ -48,6 +51,8 @@ import java.util.List; import java.util.stream.Collectors; +import static org.springframework.http.HttpHeaders.LOCATION; + @RestController @Tag(name = "Официальные обменные курсы", description = "История обменных курсов валют") @RequestMapping("/api/v1/foreign-exchange-rates") @@ -68,7 +73,10 @@ public ForeignExchangeRateRestController(ForeignExchangeRateRepository repositor @Override @GetMapping @PageableAsQueryParam - @Operation(summary = "Отобразить все", description = "Отображает все загруженные в БД информацию по обменным курсам") + @Operation(summary = "Отобразить все", description = "Отображает всю имеющуюся информацию по обменным курсам", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public Page get(@Parameter(hidden = true) Pageable pageable) { return super.get(pageable); @@ -76,7 +84,10 @@ public Page get(@Parameter(hidden = true) @GetMapping("/currency-pairs/{currency-pair}") @Operation(summary = "Отобразить по валюте", - description = "Отображает всю загруженные в БД информацию по обменному курсу одной валюте") + description = "Отображает всю имеющуюся информацию по обменному курсу заданной валютной пары", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) protected List get(@PathVariable("currency-pair") @Parameter(description = "Валютная пара") String currencyPair) { @@ -90,7 +101,9 @@ protected List get(@PathVariable("currency-pair") * see {@link AbstractRestController#get(Object)} */ @GetMapping("/currency-pairs/{currency-pair}/dates/{date}") - @Operation(summary = "Отобразить по валюте и дате") + @Operation(summary = "Отобразить по валюте и дате", responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) protected ResponseEntity get(@PathVariable("currency-pair") @Parameter(description = "Валютная пара", example = "USDRUB") String currencyPair, @@ -103,7 +116,10 @@ protected ResponseEntity get(@PathVariable("currency-pair") @Override @PostMapping - @Operation(summary = "Добавить") + @Operation(summary = "Добавить", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody ForeignExchangeRate object) { foreignExchangeRateService.invalidateCache(); return super.post(object); @@ -113,7 +129,11 @@ public ResponseEntity post(@Valid @RequestBody ForeignExchangeRate object) * see {@link AbstractRestController#put(Object, Object)} */ @PutMapping("/currency-pairs/{currency-pair}/dates/{date}") - @Operation(summary = "Обновить", description = "Обновляет информацию о курсе валюты за заданную дату") + @Operation(summary = "Обновить", description = "Обновляет информацию о курсе валюты за заданную дату", + responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("currency-pair") @Parameter(description = "Валютная пара", example = "USDRUB") String currencyPair, @@ -132,14 +152,17 @@ public ResponseEntity put(@PathVariable("currency-pair") * see {@link AbstractRestController#delete(Object)} */ @DeleteMapping("/currency-pairs/{currency-pair}/dates/{date}") - @Operation(summary = "Удалить", description = "Удаляет информацию о курсе из БД") + @Operation(summary = "Удалить", description = "Удаляет информацию о курсе из БД", + responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("currency-pair") - @Parameter(description = "Валютная пара", example = "USDRUB") - String currencyPair, - @PathVariable("date") - @Parameter(description = "Дата", example = "2021-01-23") - @DateTimeFormat(pattern = "yyyy-MM-dd") - LocalDate date) { + @Parameter(description = "Валютная пара", example = "USDRUB") + String currencyPair, + @PathVariable("date") + @Parameter(description = "Дата", example = "2021-01-23") + @DateTimeFormat(pattern = "yyyy-MM-dd") + LocalDate date) { foreignExchangeRateService.invalidateCache(); return super.delete(getId(currencyPair, date)); } diff --git a/src/main/java/ru/investbook/api/IssuerRestController.java b/src/main/java/ru/investbook/api/IssuerRestController.java index 6a6ccedb..2b4fc112 100644 --- a/src/main/java/ru/investbook/api/IssuerRestController.java +++ b/src/main/java/ru/investbook/api/IssuerRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.spacious_team.broker.pojo.Issuer; @@ -39,6 +42,8 @@ import ru.investbook.converter.EntityConverter; import ru.investbook.entity.IssuerEntity; +import static org.springframework.http.HttpHeaders.LOCATION; + @RestController @Tag(name = "Эмитенты", description = "Информация об эмитентах") @RequestMapping("/api/v1/issuers") @@ -51,7 +56,10 @@ public IssuerRestController(JpaRepository repository, Ent @Override @GetMapping @PageableAsQueryParam - @Operation(summary = "Отобразить всех") + @Operation(summary = "Отобразить всех", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public Page get(@Parameter(hidden = true) Pageable pageable) { return super.get(pageable); @@ -59,7 +67,10 @@ public Page get(@Parameter(hidden = true) @Override @GetMapping("{id}") - @Operation(summary = "Отобразить одного", description = "Отобразить информацию об эмитенте по его номеру") + @Operation(summary = "Отобразить одного", description = "Отобразить информацию об эмитенте по его номеру", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity get(@PathVariable("id") @Parameter(description = "Внутренний идентификатор эмитента") Integer id) { @@ -68,14 +79,20 @@ public ResponseEntity get(@PathVariable("id") @Override @PostMapping - @Operation(summary = "Добавить") + @Operation(summary = "Добавить", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody Issuer issuer) { return super.post(issuer); } @Override @PutMapping("{id}") - @Operation(summary = "Обновить сведения") + @Operation(summary = "Обновить сведения", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("id") @Parameter(description = "Внутренний идентификатор эмитента") Integer id, @@ -87,10 +104,12 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") - @Operation(summary = "Удалить", description = "Удаляет сведения об эмитенте из БД") + @Operation(summary = "Удалить", description = "Удаляет сведения об эмитенте из БД", responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("id") - @Parameter(description = "Внутренний идентификатор эмитента") - Integer id) { + @Parameter(description = "Внутренний идентификатор эмитента") + Integer id) { return super.delete(id); } diff --git a/src/main/java/ru/investbook/api/PortfolioCashRestController.java b/src/main/java/ru/investbook/api/PortfolioCashRestController.java index b74650ef..96d2996b 100644 --- a/src/main/java/ru/investbook/api/PortfolioCashRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioCashRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.spacious_team.broker.pojo.PortfolioCash; @@ -39,6 +42,8 @@ import ru.investbook.converter.EntityConverter; import ru.investbook.entity.PortfolioCashEntity; +import static org.springframework.http.HttpHeaders.LOCATION; + @RestController @Tag(name = "Информация по остатку денежных средств на счете") @RequestMapping("/api/v1/portfolio-cash") @@ -52,7 +57,9 @@ public PortfolioCashRestController(JpaRepository r @Override @GetMapping @PageableAsQueryParam - @Operation(summary = "Отобразить все", description = "Отображает всю имеющуюся информацию обо всех счетах") + @Operation(summary = "Отобразить все", description = "Отображает всю информацию обо всех счетах", responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public Page get(@Parameter(hidden = true) Pageable pageable) { return super.get(pageable); @@ -60,7 +67,9 @@ public Page get(@Parameter(hidden = true) @Override @GetMapping("{id}") - @Operation(summary = "Отобразить один", description = "Отображает информацию по идентификатору") + @Operation(summary = "Отобразить один", description = "Отображает информацию по идентификатору", responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity get(@PathVariable("id") @Parameter(description = "Внутренний идентификатор записи") Integer id) { @@ -69,14 +78,20 @@ public ResponseEntity get(@PathVariable("id") @Override @PostMapping - @Operation(summary = "Добавить", description = "Добавить информацию для конкретного счета") + @Operation(summary = "Добавить", description = "Добавить информацию для конкретного счета", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody PortfolioCash property) { return super.post(property); } @Override @PutMapping("{id}") - @Operation(summary = "Обновить", description = "Обновить информацию для счета") + @Operation(summary = "Обновить", description = "Обновить информацию для счета", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("id") @Parameter(description = "Внутренний идентификатор записи") Integer id, @@ -88,10 +103,12 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") - @Operation(summary = "Удалить") + @Operation(summary = "Удалить", responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("id") - @Parameter(description = "Внутренний идентификатор записи") - Integer id) { + @Parameter(description = "Внутренний идентификатор записи") + Integer id) { return super.delete(id); } diff --git a/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java b/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java index efe5fa4c..11fd8051 100644 --- a/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioPropertyRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.spacious_team.broker.pojo.PortfolioProperty; @@ -39,6 +42,8 @@ import ru.investbook.converter.EntityConverter; import ru.investbook.entity.PortfolioPropertyEntity; +import static org.springframework.http.HttpHeaders.LOCATION; + @RestController @Tag(name = "Информация по счетам") @RequestMapping("/api/v1/portfolio-properties") @@ -52,7 +57,10 @@ public PortfolioPropertyRestController(JpaRepository get(@Parameter(hidden = true) Pageable pageable) { return super.get(pageable); @@ -60,7 +68,10 @@ public Page get(@Parameter(hidden = true) @Override @GetMapping("{id}") - @Operation(summary = "Отобразить один", description = "Отображает информацию по идентификатору") + @Operation(summary = "Отобразить один", description = "Отображает информацию по идентификатору", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity get(@PathVariable("id") @Parameter(description = "Внутренний идентификатор записи") Integer id) { @@ -69,14 +80,22 @@ public ResponseEntity get(@PathVariable("id") @Override @PostMapping - @Operation(summary = "Добавить", description = "Добавить информацию для конкретного счета") + @Operation(summary = "Добавить", description = "Добавить информацию для конкретного счета", + responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody PortfolioProperty property) { return super.post(property); } @Override @PutMapping("{id}") - @Operation(summary = "Обновить", description = "Обновить информацию для счета") + @Operation(summary = "Обновить", description = "Обновить информацию для счета", + responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("id") @Parameter(description = "Внутренний идентификатор записи") Integer id, @@ -88,10 +107,13 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") - @Operation(summary = "Удалить") + @Operation(summary = "Удалить", + responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("id") - @Parameter(description = "Внутренний идентификатор записи") - Integer id) { + @Parameter(description = "Внутренний идентификатор записи") + Integer id) { return super.delete(id); } diff --git a/src/main/java/ru/investbook/api/PortfolioRestController.java b/src/main/java/ru/investbook/api/PortfolioRestController.java index f9637127..429dd659 100644 --- a/src/main/java/ru/investbook/api/PortfolioRestController.java +++ b/src/main/java/ru/investbook/api/PortfolioRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.spacious_team.broker.pojo.Portfolio; @@ -39,6 +42,8 @@ import ru.investbook.entity.PortfolioEntity; import ru.investbook.repository.PortfolioRepository; +import static org.springframework.http.HttpHeaders.LOCATION; + @RestController @Tag(name = "Счета") @RequestMapping("/api/v1/portfolios") @@ -53,7 +58,9 @@ public PortfolioRestController(PortfolioRepository repository, PortfolioConverte @Override @GetMapping @PageableAsQueryParam - @Operation(summary = "Отобразить все") + @Operation(summary = "Отобразить все", responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public Page get(@Parameter(hidden = true) Pageable pageable) { return super.get(pageable); @@ -61,7 +68,9 @@ public Page get(@Parameter(hidden = true) @Override @GetMapping("{id}") - @Operation(summary = "Отобразить один") + @Operation(summary = "Отобразить один", responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity get(@PathVariable("id") @Parameter(description = "Номер счета") String id) { @@ -70,14 +79,20 @@ public ResponseEntity get(@PathVariable("id") @Override @PostMapping - @Operation(summary = "Добавить") + @Operation(summary = "Добавить", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody Portfolio object) { return super.post(object); } @Override @PutMapping("{id}") - @Operation(summary = "Добавить") + @Operation(summary = "Обновить", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("id") @Parameter(description = "Номер счета") String id, @@ -89,10 +104,12 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") - @Operation(summary = "Удалить", description = "Удалить счет и все связанные с ним данные") + @Operation(summary = "Удалить", description = "Удалить счет и все связанные с ним данные", responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("id") - @Parameter(description = "Номер счета") - String id) { + @Parameter(description = "Номер счета") + String id) { return super.delete(id); } diff --git a/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java b/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java index 1112b9ee..e52563c1 100644 --- a/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java +++ b/src/main/java/ru/investbook/api/SecurityDescriptionRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.spacious_team.broker.pojo.SecurityDescription; @@ -39,6 +42,8 @@ import ru.investbook.entity.SecurityDescriptionEntity; import ru.investbook.repository.SecurityDescriptionRepository; +import static org.springframework.http.HttpHeaders.LOCATION; + @RestController @Tag(name = "Информация по инструментам", description = "Сектор экономики, эмитент") @RequestMapping("/api/v1/security-descriptions") @@ -53,7 +58,10 @@ public SecurityDescriptionRestController(SecurityDescriptionRepository repositor @Override @GetMapping @PageableAsQueryParam - @Operation(summary = "Отобразить все", description = "Отобразить информацию по всем инструментам") + @Operation(summary = "Отобразить все", description = "Отобразить информацию по всем инструментам", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public Page get(@Parameter(hidden = true) Pageable pageable) { return super.get(pageable); @@ -62,8 +70,10 @@ public Page get(@Parameter(hidden = true) @Override @GetMapping("{id}") - @Operation(summary = "Отобразить один", - description = "Отобразить информацию по инструменту") + @Operation(summary = "Отобразить один", description = "Отобразить информацию по инструменту", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity get(@PathVariable("id") @Parameter(description = "Идентификатор", example = "123", required = true) @@ -74,14 +84,22 @@ public ResponseEntity get(@PathVariable("id") @Override @PostMapping - @Operation(summary = "Добавить", description = "Добавить информацию об акции, облигации, деривативе или валютной паре") + @Operation(summary = "Добавить", description = "Добавить информацию об акции, облигации, деривативе или валютной паре", + responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody SecurityDescription security) { return super.post(security); } @Override @PutMapping("{id}") - @Operation(summary = "Обновить", description = "Добавить информацию об акции, облигации, деривативе или валютной паре") + @Operation(summary = "Обновить", description = "Добавить информацию об акции, облигации, деривативе или валютной паре", + responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("id") @Parameter(description = "Идентификатор", example = "123", required = true) Integer id, @@ -93,10 +111,13 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") - @Operation(summary = "Удалить", description = "Удалить информацию по инструменту") + @Operation(summary = "Удалить", description = "Удалить информацию по инструменту", + responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("id") - @Parameter(description = "Идентификатор", example = "123", required = true) - Integer id) { + @Parameter(description = "Идентификатор", example = "123", required = true) + Integer id) { return super.delete(id); } diff --git a/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java b/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java index 9d5edbf9..764d784d 100644 --- a/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/SecurityEventCashFlowRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.spacious_team.broker.pojo.SecurityEventCashFlow; @@ -41,6 +44,7 @@ import ru.investbook.report.FifoPositionsFactory; import static org.spacious_team.broker.pojo.CashFlowType.REDEMPTION; +import static org.springframework.http.HttpHeaders.LOCATION; @RestController @Tag(name = "События по бумаге", description = """ @@ -60,7 +64,9 @@ public SecurityEventCashFlowRestController(JpaRepository get(@Parameter(hidden = true) Pageable pageable) { return super.get(pageable); @@ -68,16 +74,21 @@ public Page get(@Parameter(hidden = true) @Override @GetMapping("{id}") - @Operation(summary = "Отобразить одну", description = "Отобразить выплату по идентификатору") + @Operation(summary = "Отобразить одну", description = "Отобразить выплату по идентификатору", responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity get(@PathVariable("id") - @Parameter(description = "Внутренний идентификатор выплаты в БД") + @Parameter(description = "Внутренний идентификатор выплаты") Integer id) { return super.get(id); } @Override @PostMapping - @Operation(summary = "Добавить", description = "Сохранить информацию о выплате в БД") + @Operation(summary = "Добавить", description = "Сохранить информацию о выплате", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody SecurityEventCashFlow event) { if (event.getEventType() == REDEMPTION) positionsFactory.invalidateCache(); return super.post(event); @@ -85,9 +96,12 @@ public ResponseEntity post(@Valid @RequestBody SecurityEventCashFlow event @Override @PutMapping("{id}") - @Operation(summary = "Изменить", description = "Модифицировать информацию о выплате в БД") + @Operation(summary = "Обновить", description = "Модифицировать информацию о выплате", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("id") - @Parameter(description = "Внутренний идентификатор выплаты в БД") + @Parameter(description = "Внутренний идентификатор выплаты") Integer id, @Valid @RequestBody @@ -98,10 +112,12 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") - @Operation(summary = "Удалить", description = "Удалить информацию о выплате из БД") + @Operation(summary = "Удалить", description = "Удалить информацию о выплате", responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("id") - @Parameter(description = "Внутренний идентификатор выплаты в БД") - Integer id) { + @Parameter(description = "Внутренний идентификатор выплаты") + Integer id) { positionsFactory.invalidateCache(); return super.delete(id); } diff --git a/src/main/java/ru/investbook/api/SecurityQuoteRestController.java b/src/main/java/ru/investbook/api/SecurityQuoteRestController.java index bac27a4c..e3623562 100644 --- a/src/main/java/ru/investbook/api/SecurityQuoteRestController.java +++ b/src/main/java/ru/investbook/api/SecurityQuoteRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.spacious_team.broker.pojo.SecurityQuote; @@ -39,6 +42,8 @@ import ru.investbook.converter.EntityConverter; import ru.investbook.entity.SecurityQuoteEntity; +import static org.springframework.http.HttpHeaders.LOCATION; + @RestController @Tag(name = "Котировки", description = "Котировки биржевых инструментов") @RequestMapping("/api/v1/security-quotes") @@ -52,7 +57,10 @@ public SecurityQuoteRestController(JpaRepository r @Override @GetMapping @PageableAsQueryParam - @Operation(summary = "Отобразить все", description = "Отобразить всю историю котировок по всем инструментам") + @Operation(summary = "Отобразить все", description = "Отобразить всю историю котировок по всем инструментам", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public Page get(@Parameter(hidden = true) Pageable pageable) { return super.get(pageable); @@ -61,7 +69,10 @@ public Page get(@Parameter(hidden = true) @Override @GetMapping("{id}") - @Operation(summary = "Отобразить одну", description = "Отобразить котировку по номеру записи в БД") + @Operation(summary = "Отобразить одну", description = "Отобразить котировку по номеру записи", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity get(@PathVariable("id") @Parameter(description = "Номер записи о котировке") Integer id) { @@ -70,14 +81,20 @@ public ResponseEntity get(@PathVariable("id") @Override @PostMapping - @Operation(summary = "Добавить") + @Operation(summary = "Добавить", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody SecurityQuote quote) { return super.post(quote); } @Override @PutMapping("{id}") - @Operation(summary = "Изменить") + @Operation(summary = "Обновить", responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("id") @Parameter(description = "Номер записи о котировке") Integer id, @@ -89,10 +106,12 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") - @Operation(summary = "Удалить") + @Operation(summary = "Удалить", responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("id") - @Parameter(description = "Номер записи о котировке") - Integer id) { + @Parameter(description = "Номер записи о котировке") + Integer id) { return super.delete(id); } diff --git a/src/main/java/ru/investbook/api/SecurityRestController.java b/src/main/java/ru/investbook/api/SecurityRestController.java index 28f7e377..2ac8fe49 100644 --- a/src/main/java/ru/investbook/api/SecurityRestController.java +++ b/src/main/java/ru/investbook/api/SecurityRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.spacious_team.broker.pojo.Security; @@ -39,6 +42,8 @@ import ru.investbook.entity.SecurityEntity; import ru.investbook.repository.SecurityRepository; +import static org.springframework.http.HttpHeaders.LOCATION; + @RestController @Tag(name = "Инструменты", description = "Акции, облигации, деривативы и валютные пары") @RequestMapping("/api/v1/securities") @@ -53,7 +58,10 @@ public SecurityRestController(SecurityRepository repository, SecurityConverter c @Override @GetMapping @PageableAsQueryParam - @Operation(summary = "Отобразить все", description = "Отобразить все биржевые инструменты") + @Operation(summary = "Отобразить все", description = "Отобразить все биржевые инструменты", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public Page get(@Parameter(hidden = true) Pageable pageable) { return super.get(pageable); @@ -62,8 +70,10 @@ public Page get(@Parameter(hidden = true) @Override @GetMapping("{id}") - @Operation(summary = "Отобразить один", - description = "Отобразить биржевой инструмент по внутреннему идентификатору") + @Operation(summary = "Отобразить один", description = "Отобразить биржевой инструмент по внутреннему идентификатору", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity get(@PathVariable("id") @Parameter(description = "Идентификатор", example = "123", required = true) Integer id) { @@ -73,14 +83,22 @@ public ResponseEntity get(@PathVariable("id") @Override @PostMapping - @Operation(summary = "Добавить", description = "Добавить информацию об акции, облигации, деривативе или валютной паре") + @Operation(summary = "Добавить", description = "Добавить информацию об акции, облигации, деривативе или валютной паре", + responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody Security security) { return super.post(security); } @Override @PutMapping("{id}") - @Operation(summary = "Обновить", description = "Добавить информацию об акции, облигации, деривативе или валютной паре") + @Operation(summary = "Обновить", description = "Добавить информацию об акции, облигации, деривативе или валютной паре", + responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("id") @Parameter(description = "Идентификатор", example = "123", required = true) Integer id, @@ -92,10 +110,13 @@ public ResponseEntity put(@PathVariable("id") @Override @DeleteMapping("{id}") - @Operation(summary = "Удалить", description = "Удалить сведения о биржевом инструменте и всех его сделках по всем счетам") + @Operation(summary = "Удалить", description = "Удалить сведения о биржевом инструменте и всех его сделках по всем счетам", + responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("id") - @Parameter(description = "Идентификатор", example = "123", required = true) - Integer id) { + @Parameter(description = "Идентификатор", example = "123", required = true) + Integer id) { return super.delete(id); } diff --git a/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java b/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java index b09d4dea..869b880d 100644 --- a/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java +++ b/src/main/java/ru/investbook/api/TransactionCashFlowRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.spacious_team.broker.pojo.CashFlowType; @@ -45,6 +48,8 @@ import java.util.List; +import static org.springframework.http.HttpHeaders.LOCATION; + @RestController @Tag(name = "Движения ДС по сделкам", description = "Уплаченные и вырученные суммы в сделках") @RequestMapping("/api/v1/transaction-cash-flows") @@ -64,7 +69,10 @@ public TransactionCashFlowRestController(TransactionCashFlowRepository repositor @GetMapping @PageableAsQueryParam - @Operation(summary = "Отобразить по фильтру", description = "Отобразить информацию о сделках") + @Operation(summary = "Отобразить по фильтру", description = "Отобразить информацию о сделках", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) protected Page get( @RequestParam(value = "portfolio", required = false) @Parameter(description = "Номер счета") @@ -100,7 +108,10 @@ private Page filterByEventType(Page transactio @Override @GetMapping("{id}") - @Operation(summary = "Отобразить одну", description = "Отобразить информацию о конкретной сделке") + @Operation(summary = "Отобразить одну", description = "Отобразить информацию о конкретной сделке", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity get(@PathVariable("id") @Parameter(description = "Внутренний идентификатор сделки") Integer id) { @@ -109,7 +120,11 @@ public ResponseEntity get(@PathVariable("id") @Override @PostMapping - @Operation(summary = "Добавить", description = "Добавить информацию об об объемах движения ДС по сделке") + @Operation(summary = "Добавить", description = "Добавить информацию об об объемах движения ДС по сделке", + responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody TransactionCashFlow object) { return super.post(object); } @@ -119,7 +134,11 @@ public ResponseEntity post(@Valid @RequestBody TransactionCashFlow object) */ @Override @PutMapping("{id}") - @Operation(summary = "Обновить", description = "Обновить информацию об об объемах движения ДС по сделке") + @Operation(summary = "Обновить", description = "Обновить информацию об об объемах движения ДС по сделке", + responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("id") @Parameter(description = "Внутренний идентификатор сделки") Integer id, @@ -136,10 +155,13 @@ public ResponseEntity put(@PathVariable("id") @DeleteMapping("{id}") @Operation(summary = "Удалить", description = """ Удалить информацию об об объемах движения ДС по сделке. Сама сделка не удаляется, ее нужно удалить своим API - """) + """, + responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("id") - @Parameter(description = "Внутренний идентификатор сделки") - Integer id) { + @Parameter(description = "Внутренний идентификатор сделки") + Integer id) { return super.delete(id); } diff --git a/src/main/java/ru/investbook/api/TransactionRestController.java b/src/main/java/ru/investbook/api/TransactionRestController.java index cac5a36a..8020eefb 100644 --- a/src/main/java/ru/investbook/api/TransactionRestController.java +++ b/src/main/java/ru/investbook/api/TransactionRestController.java @@ -20,6 +20,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.spacious_team.broker.pojo.Transaction; @@ -44,6 +47,8 @@ import java.util.List; +import static org.springframework.http.HttpHeaders.LOCATION; + @RestController @Tag(name = "Сделки", description = "Операции купли/продажи биржевых инструментов") @RequestMapping("/api/v1/transactions") @@ -63,7 +68,10 @@ public TransactionRestController(TransactionRepository repository, @GetMapping @PageableAsQueryParam - @Operation(summary = "Отобразить по фильтру", description = "Отображает сделки по счетам") + @Operation(summary = "Отобразить по фильтру", description = "Отображает сделки по счетам", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public Page get(@RequestParam(value = "portfolio", required = false) @Parameter(description = "Идентификатор счета брокера") String portfolio, @@ -106,7 +114,10 @@ private Page getByPortfolioAndTradeId(String portfolio, String trad */ @Override @GetMapping("{id}") - @Operation(summary = "Отобразить одну", description = "Отображает одну сделку") + @Operation(summary = "Отобразить одну", description = "Отображает одну сделку", + responses = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity get(@PathVariable("id") @Parameter(description = "Внутренний идентификатор сделки") Integer id) { @@ -115,7 +126,11 @@ public ResponseEntity get(@PathVariable("id") @Override @PostMapping - @Operation(summary = "Добавить", description = "Сохраняет новую сделку в БД") + @Operation(summary = "Добавить", description = "Сохраняет новую сделку", + responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "409"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity post(@Valid @RequestBody Transaction object) { positionsFactory.invalidateCache(); return super.post(object); @@ -126,7 +141,11 @@ public ResponseEntity post(@Valid @RequestBody Transaction object) { */ @Override @PutMapping("{id}") - @Operation(summary = "Обновить параметры", description = "Обновляет параметры указанной сделки") + @Operation(summary = "Обновить параметры", description = "Обновляет параметры указанной сделки", + responses = { + @ApiResponse(responseCode = "201", headers = @Header(name = LOCATION)), + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity put(@PathVariable("id") @Parameter(description = "Внутренний идентификатор сделки") Integer id, @@ -142,10 +161,13 @@ public ResponseEntity put(@PathVariable("id") */ @Override @DeleteMapping("{id}") - @Operation(summary = "Удалить", description = "Удаляет указанную сделку") + @Operation(summary = "Удалить", description = "Удаляет указанную сделку", + responses = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "500", content = @Content)}) public ResponseEntity delete(@PathVariable("id") - @Parameter(description = "Внутренний идентификатор сделки") - Integer id) { + @Parameter(description = "Внутренний идентификатор сделки") + Integer id) { positionsFactory.invalidateCache(); return super.delete(id); } diff --git a/src/main/resources/application-core.properties b/src/main/resources/application-core.properties index 7174d7d1..3b853b6c 100644 --- a/src/main/resources/application-core.properties +++ b/src/main/resources/application-core.properties @@ -62,4 +62,5 @@ spring.jmx.enabled = false springdoc.swagger-ui.syntaxHighlight.activated = false # По умолчанию свернуть все endpoint-ы на странице Swagger UI -springdoc.swagger-ui.doc-expansion = none \ No newline at end of file +springdoc.swagger-ui.doc-expansion = none +springdoc.default-produces-media-type=application/json From 1211600bff87518d77b7f2f2122d1e9960b5ef33 Mon Sep 17 00:00:00 2001 From: vananiev Date: Mon, 2 Sep 2024 11:46:10 +0300 Subject: [PATCH 38/40] update spring boot to 3.3.3 and update other deps --- README-en.md | 4 ++-- README.md | 2 +- pom.xml | 10 +++++----- src/main/resources/application-dev.properties | 2 -- src/main/resources/application-h2.properties | 1 - src/main/resources/application-mariadb.properties | 1 - 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/README-en.md b/README-en.md index c6ce3813..675b0632 100644 --- a/README-en.md +++ b/README-en.md @@ -2,7 +2,7 @@ [](README.md)
[![java-version](https://img.shields.io/badge/java-22-brightgreen?style=flat-square)](https://openjdk.org/) -[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.3.0-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) +[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.3.3-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) [![github-closed-pull-requests](https://img.shields.io/github/issues-pr-closed/spacious-team/investbook?style=flat-square&color=brightgreen)](https://github.com/spacious-team/investbook/pulls?q=is%3Apr+is%3Aclosed) [![github-workflow-status](https://img.shields.io/github/actions/workflow/status/spacious-team/investbook/publish-docker.yml?style=flat-square&branch=master)](https://github.com/spacious-team/investbook/actions/workflows/publish-docker.yml) @@ -114,7 +114,7 @@ has the following advantages: if necessary, it will allow you to painlessly transfer the accumulated data to another investment accounting application. ### Brokers -The application analyzes reports from brokers Tinkoff (xlsx), Sberbank (xlsx), VTB (xls), Promsvyazbank (xlsx, xml) +The application analyzes reports from brokers TBank / Tinkoff (xlsx), Sberbank (xlsx), VTB (xls), Promsvyazbank (xlsx, xml) and Your Broker / Uralsib (zip with xls). If your account is opened with another broker, write to [us](https://t.me/investbook_support). You can also use it already on your version of the application [forms](src/main/asciidoc/investbook-forms.adoc) for entering information or diff --git a/README.md b/README.md index 72f5610e..816a4de0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [](README.md)
[![java-version](https://img.shields.io/badge/java-22-brightgreen?style=flat-square)](https://openjdk.org/) -[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.3.0-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) +[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.3.3-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) [![github-closed-pull-requests](https://img.shields.io/github/issues-pr-closed/spacious-team/investbook?style=flat-square&color=brightgreen)](https://github.com/spacious-team/investbook/pulls?q=is%3Apr+is%3Aclosed) [![github-workflow-status](https://img.shields.io/github/actions/workflow/status/spacious-team/investbook/publish-docker.yml?style=flat-square&branch=master)](https://github.com/spacious-team/investbook/actions/workflows/publish-docker.yml) diff --git a/pom.xml b/pom.xml index 98269d81..f803ec75 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.0 + 3.3.3 ru.investbook @@ -151,7 +151,7 @@ org.testng testng - 7.7.0 + 7.10.2 test @@ -204,18 +204,18 @@ org.jsoup jsoup - 1.15.4 + 1.18.1 org.projectlombok lombok - 1.18.32 + 1.18.34 true com.github.albfernandez juniversalchardet - 2.4.0 + 2.5.0 org.apache.poi diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index e3f3269d..a0aa2c38 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -18,7 +18,6 @@ # MariaDB #spring.datasource.url = jdbc:mariadb://localhost:3306/portfolio?createDatabaseIfNotExist=true&serverTimezone=Europe/Moscow -#spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MariaDBDialect #spring.datasource.username = root #spring.datasource.password = 123456 #spring.datasource.driver-class-name=org.mariadb.jdbc.Driver @@ -26,7 +25,6 @@ # H2 spring.datasource.url=jdbc:h2:file:~/investbook3-test;mode=mysql;non_keywords=value -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.datasource.username = sa spring.datasource.password = diff --git a/src/main/resources/application-h2.properties b/src/main/resources/application-h2.properties index b0454f1f..9638ed76 100644 --- a/src/main/resources/application-h2.properties +++ b/src/main/resources/application-h2.properties @@ -17,7 +17,6 @@ # spring.datasource.url=jdbc:h2:file:~/investbook/investbook3;mode=mysql;non_keywords=value -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.datasource.username = sa spring.datasource.password = diff --git a/src/main/resources/application-mariadb.properties b/src/main/resources/application-mariadb.properties index 682d3d9b..995dc390 100644 --- a/src/main/resources/application-mariadb.properties +++ b/src/main/resources/application-mariadb.properties @@ -1,5 +1,4 @@ spring.datasource.url=jdbc:mariadb://localhost:3306/portfolio?createDatabaseIfNotExist=true&serverTimezone=Europe/Moscow -spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MariaDBDialect spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=org.mariadb.jdbc.Driver From 7861c9e8178e186a8b1f356a4582255c8e7e9c4d Mon Sep 17 00:00:00 2001 From: Sergei Prokofev Date: Mon, 9 Sep 2024 12:40:46 +0400 Subject: [PATCH 39/40] wrote few unit tests for moexClient --- .../service/moex/MoexIssClientTest.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/test/java/ru/investbook/service/moex/MoexIssClientTest.java diff --git a/src/test/java/ru/investbook/service/moex/MoexIssClientTest.java b/src/test/java/ru/investbook/service/moex/MoexIssClientTest.java new file mode 100644 index 00000000..22065098 --- /dev/null +++ b/src/test/java/ru/investbook/service/moex/MoexIssClientTest.java @@ -0,0 +1,75 @@ +/* + * InvestBook + * Copyright (C) 2024 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package ru.investbook.service.moex; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.spacious_team.broker.pojo.SecurityQuote; +import org.springframework.web.client.RestTemplate; + +import java.util.Optional; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@ExtendWith(MockitoExtension.class) +public class MoexIssClientTest { + + @Mock + MoexDerivativeCodeService moexDerivativeCodeService; + RestTemplate restTemplate = new RestTemplate(); + MoexIssClientImpl moexIssClient; + + @BeforeEach + public void setUp() { + moexIssClient = new MoexIssClientImpl(moexDerivativeCodeService, restTemplate); + } + + @ParameterizedTest + @MethodSource("secids") + public void getMarket(String secid) { + Optional result = moexIssClient.getMarket(secid); + Assertions.assertTrue(result.isPresent()); + } + + @ParameterizedTest + @MethodSource("secids") + public void getIsin(String secid) { + Optional result = moexIssClient.getIsin(secid); + Assertions.assertTrue(result.isPresent()); + } + + @ParameterizedTest + @MethodSource("secids") + public void getQuote(String secid) { + Optional market = moexIssClient.getMarket(secid); + if (market.isPresent()) { + Optional result = moexIssClient.getQuote(secid, market.get()); + Assertions.assertTrue(result.isPresent()); + } + } + + public String[] secids() { + return new String[] {"AFLT", "OZON", "MGNT", "DSKY", "MVID"}; + } +} From 8aa4d459531a938d0da6ea5d29b10e263cb52746 Mon Sep 17 00:00:00 2001 From: vananiev Date: Sun, 29 Sep 2024 19:58:22 +0300 Subject: [PATCH 40/40] update spring boot to 3.3.4 and update other deps --- .mvn/wrapper/maven-wrapper.properties | 5 +- README-en.md | 2 +- README.md | 2 +- mvnw | 435 ++++++++++++-------------- mvnw.cmd | 304 ++++++++---------- pom.xml | 8 +- 6 files changed, 326 insertions(+), 430 deletions(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 63f9ec6d..f95f1ee8 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -14,5 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip diff --git a/README-en.md b/README-en.md index 675b0632..e91e26c6 100644 --- a/README-en.md +++ b/README-en.md @@ -2,7 +2,7 @@ [](README.md)
[![java-version](https://img.shields.io/badge/java-22-brightgreen?style=flat-square)](https://openjdk.org/) -[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.3.3-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) +[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.3.4-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) [![github-closed-pull-requests](https://img.shields.io/github/issues-pr-closed/spacious-team/investbook?style=flat-square&color=brightgreen)](https://github.com/spacious-team/investbook/pulls?q=is%3Apr+is%3Aclosed) [![github-workflow-status](https://img.shields.io/github/actions/workflow/status/spacious-team/investbook/publish-docker.yml?style=flat-square&branch=master)](https://github.com/spacious-team/investbook/actions/workflows/publish-docker.yml) diff --git a/README.md b/README.md index 816a4de0..48c62cc5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [](README.md)
[![java-version](https://img.shields.io/badge/java-22-brightgreen?style=flat-square)](https://openjdk.org/) -[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.3.3-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) +[![spring-boot-version](https://img.shields.io/badge/spring--boot-3.3.4-brightgreen?style=flat-square)](https://github.com/spring-projects/spring-boot/releases) [![hits-of-code](https://img.shields.io/badge/dynamic/json?style=flat-square&color=lightblue&label=hits-of-code&url=https://hitsofcode.com/github/spacious-team/investbook/json?branch=develop&query=$.count)](https://hitsofcode.com/github/spacious-team/investbook/view?branch=develop) [![github-closed-pull-requests](https://img.shields.io/github/issues-pr-closed/spacious-team/investbook?style=flat-square&color=brightgreen)](https://github.com/spacious-team/investbook/pulls?q=is%3Apr+is%3Aclosed) [![github-workflow-status](https://img.shields.io/github/actions/workflow/status/spacious-team/investbook/publish-docker.yml?style=flat-square&branch=master)](https://github.com/spacious-team/investbook/actions/workflows/publish-docker.yml) diff --git a/mvnw b/mvnw index 8d937f4c..19529ddf 100644 --- a/mvnw +++ b/mvnw @@ -19,290 +19,241 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir +# Apache Maven Wrapper startup batch script, version 3.3.2 # # Optional ENV vars # ----------------- -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /usr/local/etc/mavenrc ] ; then - . /usr/local/etc/mavenrc - fi - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false +# OS specific support. +native_path() { printf %s\\n "$1"; } case "$(uname)" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME - else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME - fi - fi - ;; +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; esac -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=$(java-config --jre-home) - fi -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --unix "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --unix "$CLASSPATH") -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && - JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="$(which javac)" - if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=$(which readlink) - if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then - if $darwin ; then - javaHome="$(dirname "\"$javaExecutable\"")" - javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" - else - javaExecutable="$(readlink -f "\"$javaExecutable\"")" - fi - javaHome="$(dirname "\"$javaExecutable\"")" - javaHome=$(expr "$javaHome" : '\(.*\)/bin') - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" else JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi fi else - JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi fi +} - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=$(cd "$wdir/.." || exit 1; pwd) - fi - # end of workaround +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" done - printf '%s' "$(cd "$basedir" || exit 1; pwd)" + printf %x\\n $h } -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - # Remove \r in case we run on Windows within Git Bash - # and check out the repository with auto CRLF management - # enabled. Otherwise, we may read lines that are delimited with - # \r\n and produce $'-Xarg\r' rather than -Xarg due to word - # splitting rules. - tr -s '\r\n' ' ' < "$1" - fi +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 } -log() { - if [ "$MVNW_VERBOSE" = true ]; then - printf '%s\n' "$1" - fi +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" } -BASE_DIR=$(find_maven_basedir "$(dirname "$0")") -if [ -z "$BASE_DIR" ]; then - exit 1; +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" fi -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR -log "$MAVEN_PROJECTBASEDIR" +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" -if [ -r "$wrapperJarPath" ]; then - log "Found $wrapperJarPath" +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT else - log "Couldn't find $wrapperJarPath, downloading it ..." + die "cannot create temp dir" +fi - if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - fi - while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') - case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; - esac - done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" - log "Downloading from: $wrapperUrl" +mkdir -p -- "${MAVEN_HOME%/*}" - if $cygwin; then - wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") - fi +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - if command -v wget > /dev/null; then - log "Found wget ... using wget" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - log "Found curl ... using curl" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - fi - else - log "Falling back to using Java to download" - javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaSource=$(cygpath --path --windows "$javaSource") - javaClass=$(cygpath --path --windows "$javaClass") - fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then - log " - Compiling MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/javac" "$javaSource") - fi - if [ -e "$javaClass" ]; then - log " - Running MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" - fi - fi - fi +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" fi -########################################################################################## -# End of extension -########################################################################################## -# If specified, validate the SHA-256 sum of the Maven wrapper jar file -wrapperSha256Sum="" -while IFS="=" read -r key value; do - case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; - esac -done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" -if [ -n "$wrapperSha256Sum" ]; then - wrapperSha256Result=false - if command -v sha256sum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then - wrapperSha256Result=true +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true fi - elif command -v shasum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then - wrapperSha256Result=true + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true fi else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." - echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi - if [ $wrapperSha256Result = false ]; then - echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 - echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 - echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 exit 1 fi fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --windows "$CLASSPATH") - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -# shellcheck disable=SC2086 # safe args -exec "$JAVACMD" \ - $MAVEN_OPTS \ - $MAVEN_DEBUG_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd index c4586b56..249bdf38 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,3 +1,4 @@ +<# : batch portion @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @@ -18,188 +19,131 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir +@REM Apache Maven Wrapper startup batch script, version 3.3.2 @REM @REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output @REM ---------------------------------------------------------------------------- -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file -SET WRAPPER_SHA_256_SUM="" -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) ) -IF NOT %WRAPPER_SHA_256_SUM%=="" ( - powershell -Command "&{"^ - "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ - "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ - " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ - " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ - " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ - " exit 1;"^ - "}"^ - "}" - if ERRORLEVEL 1 goto error -) - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml index f803ec75..bc5b42a7 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.3 + 3.3.4 ru.investbook @@ -209,7 +209,7 @@ org.projectlombok lombok - 1.18.34 + provided true @@ -253,7 +253,7 @@ org.asciidoctor asciidoctor-maven-plugin - 2.0.0 + 3.0.0 generate-html-doc @@ -277,7 +277,7 @@ org.codehaus.mojo jaxb2-maven-plugin - 3.1.0 + 3.2.0 xsd-to-java