From e0a52ad169126656394c4047f8a0823100fae55f Mon Sep 17 00:00:00 2001 From: semenykhin Date: Mon, 6 Dec 2021 18:15:43 +0200 Subject: [PATCH 1/5] Start next iteration with 13.4-SNAPSHOT --- gateway-app-embedded/pom.xml | 2 +- gateway-app/pom.xml | 2 +- ledgers-rest-client/pom.xml | 2 +- pom.xml | 2 +- xs2a-connector-embedded/pom.xml | 2 +- xs2a-connector-oauth-service/pom.xml | 2 +- xs2a-connector-remote/pom.xml | 2 +- xs2a-connector/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gateway-app-embedded/pom.xml b/gateway-app-embedded/pom.xml index 88e22d56..a7f1ca7b 100644 --- a/gateway-app-embedded/pom.xml +++ b/gateway-app-embedded/pom.xml @@ -21,7 +21,7 @@ xs2a-connector-examples de.adorsys.ledgers - 13.3 + 13.4-SNAPSHOT 4.0.0 diff --git a/gateway-app/pom.xml b/gateway-app/pom.xml index f740594a..53e80629 100644 --- a/gateway-app/pom.xml +++ b/gateway-app/pom.xml @@ -5,7 +5,7 @@ de.adorsys.ledgers xs2a-connector-examples - 13.3 + 13.4-SNAPSHOT .. gateway-app diff --git a/ledgers-rest-client/pom.xml b/ledgers-rest-client/pom.xml index 1c35f3c2..cf8e4284 100644 --- a/ledgers-rest-client/pom.xml +++ b/ledgers-rest-client/pom.xml @@ -5,7 +5,7 @@ de.adorsys.ledgers xs2a-connector-examples - 13.3 + 13.4-SNAPSHOT .. ledgers-rest-client diff --git a/pom.xml b/pom.xml index 842f6c5a..96ee3c25 100755 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 4.0.0 de.adorsys.ledgers xs2a-connector-examples - 13.3 + 13.4-SNAPSHOT pom XS2A Connector Examples diff --git a/xs2a-connector-embedded/pom.xml b/xs2a-connector-embedded/pom.xml index a6edba4d..b8e20d88 100644 --- a/xs2a-connector-embedded/pom.xml +++ b/xs2a-connector-embedded/pom.xml @@ -21,7 +21,7 @@ xs2a-connector-examples de.adorsys.ledgers - 13.3 + 13.4-SNAPSHOT 4.0.0 diff --git a/xs2a-connector-oauth-service/pom.xml b/xs2a-connector-oauth-service/pom.xml index 903a3523..4af819e3 100644 --- a/xs2a-connector-oauth-service/pom.xml +++ b/xs2a-connector-oauth-service/pom.xml @@ -21,7 +21,7 @@ xs2a-connector-examples de.adorsys.ledgers - 13.3 + 13.4-SNAPSHOT 4.0.0 diff --git a/xs2a-connector-remote/pom.xml b/xs2a-connector-remote/pom.xml index a491aadc..441e66bb 100644 --- a/xs2a-connector-remote/pom.xml +++ b/xs2a-connector-remote/pom.xml @@ -5,7 +5,7 @@ de.adorsys.ledgers xs2a-connector-examples - 13.3 + 13.4-SNAPSHOT .. diff --git a/xs2a-connector/pom.xml b/xs2a-connector/pom.xml index 288cc5eb..a252f155 100644 --- a/xs2a-connector/pom.xml +++ b/xs2a-connector/pom.xml @@ -5,7 +5,7 @@ de.adorsys.ledgers xs2a-connector-examples - 13.3 + 13.4-SNAPSHOT .. From 8218fa8563b35794566005275233de015de6be55 Mon Sep 17 00:00:00 2001 From: semenykhin Date: Mon, 6 Dec 2021 19:00:15 +0200 Subject: [PATCH 2/5] xs2a version changed, empty release notes added --- doc/release_notes/Release_notes_13.4.adoc | 3 +++ pom.xml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 doc/release_notes/Release_notes_13.4.adoc diff --git a/doc/release_notes/Release_notes_13.4.adoc b/doc/release_notes/Release_notes_13.4.adoc new file mode 100644 index 00000000..e7349939 --- /dev/null +++ b/doc/release_notes/Release_notes_13.4.adoc @@ -0,0 +1,3 @@ += Release notes v.13.4 + +== Table of Contents diff --git a/pom.xml b/pom.xml index 96ee3c25..771ded7f 100755 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,7 @@ ${project.basedir} - 13.3 + 13.4-SNAPSHOT 4.17 From 3985a36a91d9d0abc9dcb2a2c7fb970985500811 Mon Sep 17 00:00:00 2001 From: Serhii Semenykhin Date: Tue, 14 Dec 2021 13:41:31 +0100 Subject: [PATCH 3/5] transactions file writing implemented in async mode, technical keycloak --- doc/release_notes/Release_notes_13.4_SSE.adoc | 23 ++++ .../src/main/resources/application.yml | 9 +- .../src/main/resources/application.yml | 8 +- .../exception/FileManagementException.java | 25 ++++ .../spi/file/util/DeleteFileRunnable.java | 54 ++++++++ .../spi/file/util/FileManagementService.java | 45 +++++++ .../util/FileManagementServiceSimple.java | 81 ++++++++++++ .../spi/file/util/WriteFileRunnable.java | 44 +++++++ .../connector/spi/impl/AccountSpiImpl.java | 104 ++++++++++----- .../src/main/resources/mock-data.properties | 2 +- ...ctionsFileManagementServiceSimpleTest.java | 59 +++++++++ .../spi/impl/AccountSpiImplTest.java | 123 ++++++++++++++---- .../json/spi/impl/spi-transactions.json | 1 + 13 files changed, 519 insertions(+), 59 deletions(-) create mode 100644 doc/release_notes/Release_notes_13.4_SSE.adoc create mode 100644 xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/exception/FileManagementException.java create mode 100644 xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/DeleteFileRunnable.java create mode 100644 xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/FileManagementService.java create mode 100644 xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/FileManagementServiceSimple.java create mode 100644 xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/WriteFileRunnable.java create mode 100644 xs2a-connector/src/test/java/de/adorsys/aspsp/xs2a/connector/spi/file/TransactionsFileManagementServiceSimpleTest.java create mode 100644 xs2a-connector/src/test/resources/json/spi/impl/spi-transactions.json diff --git a/doc/release_notes/Release_notes_13.4_SSE.adoc b/doc/release_notes/Release_notes_13.4_SSE.adoc new file mode 100644 index 00000000..5bc35d27 --- /dev/null +++ b/doc/release_notes/Release_notes_13.4_SSE.adoc @@ -0,0 +1,23 @@ += Release notes v.13.4 + +== Table of Contents + +* Implemented writing transactions data into file asynchronous + +* Technical password from property `xs2a.funds-confirmation-user-password` changed + +== Implemented writing transactions data into file asynchronous + +From now on, content of downloaded transaction file is no more mocked, but the real data got during Read Transaction List +request. Transaction file writing is being performed in a separate thread to increase performance and reduce response time. +New properties were added into `application.yml` : + +* `xs2a.download.files.dir` - path to directory, where files are being created for downloading +* `xs2a.download.files.cleanup.delay_s` - time in seconds, specifies how long download link will be valid after the first retrieval request. +When specified time passes, file and its parent directory will be deleted, all next requests by the same download +link will cause response with code 400 `Resource Blocked` + +== Technical password from property `xs2a.funds-confirmation-user-password` changed + +Property value `xs2a.funds-confirmation-user-password` changed from `12345` to `admin123` to handle properly +ASPSP PIIS consents. diff --git a/gateway-app-embedded/src/main/resources/application.yml b/gateway-app-embedded/src/main/resources/application.yml index 6bd4eb1d..f044adf7 100644 --- a/gateway-app-embedded/src/main/resources/application.yml +++ b/gateway-app-embedded/src/main/resources/application.yml @@ -47,7 +47,14 @@ keycloak: secret: a61a81cd-7178-40d8-8386-ed02791e6592 #Here should be personal generated secret for client (swap public to 'private?' generate secret, swap to public again) xs2a.funds-confirmation-user-login: admin -xs2a.funds-confirmation-user-password: 12345 +xs2a.funds-confirmation-user-password: admin123 + +# FILE DOWNLOADING RELATED PROPERTIES +#Filedir for the files which should be accessible by downloadLink, default value "/tmp/XS2A" +xs2a.download.files.dir: /tmp/XS2A +#Delay in seconds when already downloaded file and its parent dir will be deleted, default value 30 +xs2a.download.files.cleanup.delay_s: 30 + spring: application.name: ledgers-xs2a-gateway diff --git a/gateway-app/src/main/resources/application.yml b/gateway-app/src/main/resources/application.yml index ecff080c..7143590e 100644 --- a/gateway-app/src/main/resources/application.yml +++ b/gateway-app/src/main/resources/application.yml @@ -37,7 +37,13 @@ keycloak: xs2a.swagger.psd2.api.location: #/psd2-api-1.2-Update-2018-08-18-non-oauth.yaml xs2a.funds-confirmation-user-login: admin -xs2a.funds-confirmation-user-password: 12345 +xs2a.funds-confirmation-user-password: admin123 + +# FILE DOWNLOADING RELATED PROPERTIES +#Filedir for the accessible by 'downloadLink' files, default value "/tmp/XS2A" +xs2a.download.files.dir: /tmp/XS2A/ +#Delay in seconds when already downloaded file and its parent dir will be deleted, default value 30 +xs2a.download.files.cleanup.delay_s: 30 #TanEncryption application: diff --git a/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/exception/FileManagementException.java b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/exception/FileManagementException.java new file mode 100644 index 00000000..5ee130c6 --- /dev/null +++ b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/exception/FileManagementException.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018-2021 adorsys GmbH & Co KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.adorsys.aspsp.xs2a.connector.spi.file.exception; + +import java.io.IOException; + +public class FileManagementException extends IOException { + public FileManagementException(String errorMessage) { + super(errorMessage); + } +} diff --git a/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/DeleteFileRunnable.java b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/DeleteFileRunnable.java new file mode 100644 index 00000000..e7e67418 --- /dev/null +++ b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/DeleteFileRunnable.java @@ -0,0 +1,54 @@ +/* + * Copyright 2018-2021 adorsys GmbH & Co KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.adorsys.aspsp.xs2a.connector.spi.file.util; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.nio.file.Path; + +@Slf4j +@AllArgsConstructor +@SuppressWarnings("PMD.ShortMethodName") +public class DeleteFileRunnable implements Runnable { + private final String downloadLink; + private final int deleteFileDelay; + + @Override + public void run() { + Path filePath = Path.of(downloadLink); + File file = new File(filePath.toString()); + + Path parentDirectoryPath = filePath.getParent(); + File parentDirectory = new File(parentDirectoryPath.toString()); + + try { + log.info("File {} is scheduled to be deleted in {} seconds", filePath, deleteFileDelay); + Thread.sleep(deleteFileDelay * 1000L); + FileUtils.deleteQuietly(file); + log.info("File deleted. File list in directory {} before deleting: {}", parentDirectoryPath, parentDirectory.list()); + log.info("Directory {} is scheduled to be deleted in {} seconds", parentDirectoryPath, deleteFileDelay); + Thread.sleep(deleteFileDelay * 1000L); + FileUtils.deleteDirectory(parentDirectory); + log.info("Directory deleted: {}", parentDirectoryPath); + } catch (Exception e) { + log.error("Delete file by Download Id failed (IOException): Decrypted Download id: [{}], message {}, exception: {}", downloadLink, e.getMessage(), e); + } + } +} diff --git a/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/FileManagementService.java b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/FileManagementService.java new file mode 100644 index 00000000..171df504 --- /dev/null +++ b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/FileManagementService.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018-2021 adorsys GmbH & Co KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.adorsys.aspsp.xs2a.connector.spi.file.util; + +import de.adorsys.aspsp.xs2a.connector.spi.file.exception.FileManagementException; +import org.springframework.core.io.Resource; + +public interface FileManagementService { + /** + * Stores the file and returns its identifier for further access + * @param resource is a Resource representation of input data to be stored + * @param filename is a name of file to be saved + * @return download link being used for this file retrieving + * @throws FileManagementException in case of errors during file creation and data writing + */ + String saveFileAndBuildDownloadLink(Resource resource, String filename) throws FileManagementException; + + /** + * Returns file by its downloadLink, returned after execution of `saveFileAndBuildDownloadLink(Resource resource, String filename)` method + * @param downloadLink is an identifier of requested file + * @return Resource as a representation of a requested file + * @throws FileManagementException in case of error during file reading or retrieving + */ + Resource getFileByDownloadLink(String downloadLink) throws FileManagementException; + + /** + * Deletes file by downloadLink + * @param downloadLink is an identifier of the file to be deleted. + */ + void deleteFileByDownloadLink(String downloadLink); +} diff --git a/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/FileManagementServiceSimple.java b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/FileManagementServiceSimple.java new file mode 100644 index 00000000..d86f74bb --- /dev/null +++ b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/FileManagementServiceSimple.java @@ -0,0 +1,81 @@ +/* + * Copyright 2018-2021 adorsys GmbH & Co KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.adorsys.aspsp.xs2a.connector.spi.file.util; + +import de.adorsys.aspsp.xs2a.connector.spi.file.exception.FileManagementException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +@Service +@Slf4j +public class FileManagementServiceSimple implements FileManagementService { + @Value("${xs2a.download.files.cleanup.delay_s:30}") + public int deleteFileDelay; + + @Value("${xs2a.download.files.dir:/tmp/XS2A}") + private String configurationPath; + + @Override + public String saveFileAndBuildDownloadLink(Resource resource, String filename) throws FileManagementException { + + try { + byte[] bytes = resource.getInputStream().readAllBytes(); + Path dirPath = Path.of(configurationPath); + Files.createDirectories(dirPath); + Path dir = Files.createTempDirectory(dirPath, StringUtils.EMPTY); + Path fileToCreatePath = dir.resolve(filename); + Path newFilePath = Files.createFile(fileToCreatePath); + File file = newFilePath.toFile(); + + WriteFileRunnable writeFileRunnable = new WriteFileRunnable(file, bytes); + Thread asyncFileWrite = new Thread(writeFileRunnable); + asyncFileWrite.start(); + + log.info("Bytes read: [{}]", bytes.length); + return file.getAbsolutePath(); + } catch (IOException e) { + log.error("Save file and build Download Link failed (IOException): message {}, exception {}", e.getMessage(), e); + throw new FileManagementException(e.getMessage()); + } + } + + @Override + public Resource getFileByDownloadLink(String downloadLink) throws FileManagementException { + Path path = Path.of(downloadLink); + if (path.toFile().exists()) { + return new FileSystemResource(path); + } + log.error("File does not exist: [{}]", downloadLink); + throw new FileManagementException("Requested file does not exist"); + } + + @Override + public void deleteFileByDownloadLink(String downloadLink) { + DeleteFileRunnable deleteFileRunnable = new DeleteFileRunnable(downloadLink, deleteFileDelay); + Thread asyncFileDelete = new Thread(deleteFileRunnable); + asyncFileDelete.start(); + } +} diff --git a/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/WriteFileRunnable.java b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/WriteFileRunnable.java new file mode 100644 index 00000000..c0ddd0d0 --- /dev/null +++ b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/file/util/WriteFileRunnable.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018-2021 adorsys GmbH & Co KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.adorsys.aspsp.xs2a.connector.spi.file.util; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +@Slf4j +@AllArgsConstructor +@SuppressWarnings("PMD.ShortMethodName") +public class WriteFileRunnable implements Runnable { + private final File file; + private final byte[] bytes; + + @Override + public void run() { + file.setReadable(false); + try { + FileUtils.writeByteArrayToFile(file, bytes); + } catch (IOException e) { + log.error("Unable to write data into the file: {}", file.getAbsolutePath()); + } finally { + file.setReadable(true); + } + } +} diff --git a/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/impl/AccountSpiImpl.java b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/impl/AccountSpiImpl.java index 5f3e0655..89398c8d 100644 --- a/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/impl/AccountSpiImpl.java +++ b/xs2a-connector/src/main/java/de/adorsys/aspsp/xs2a/connector/spi/impl/AccountSpiImpl.java @@ -20,6 +20,8 @@ import de.adorsys.aspsp.xs2a.connector.account.OwnerNameService; import de.adorsys.aspsp.xs2a.connector.mock.IbanResolverMockService; import de.adorsys.aspsp.xs2a.connector.spi.converter.LedgersSpiAccountMapper; +import de.adorsys.aspsp.xs2a.connector.spi.file.exception.FileManagementException; +import de.adorsys.aspsp.xs2a.connector.spi.file.util.FileManagementService; import de.adorsys.aspsp.xs2a.connector.spi.impl.service.TransactionLinksService; import de.adorsys.ledgers.middleware.api.domain.account.AccountDetailsTO; import de.adorsys.ledgers.middleware.api.domain.account.TransactionTO; @@ -27,6 +29,7 @@ import de.adorsys.ledgers.rest.client.AccountRestClient; import de.adorsys.ledgers.rest.client.AuthRequestInterceptor; import de.adorsys.ledgers.util.domain.CustomPageImpl; +import de.adorsys.psd2.mapper.Xs2aObjectMapper; import de.adorsys.psd2.xs2a.core.ais.BookingStatus; import de.adorsys.psd2.xs2a.core.consent.AisConsentRequestType; import de.adorsys.psd2.xs2a.core.error.MessageErrorCode; @@ -43,7 +46,6 @@ import de.adorsys.psd2.xs2a.spi.domain.response.SpiResponse; import de.adorsys.psd2.xs2a.spi.service.AccountSpi; import feign.FeignException; -import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; @@ -51,8 +53,11 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; @@ -62,10 +67,13 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.Month; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Currency; +import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; -@Slf4j @Component @PropertySource("classpath:mock-data.properties") public class AccountSpiImpl implements AccountSpi { @@ -83,19 +91,22 @@ public class AccountSpiImpl implements AccountSpi { private final IbanResolverMockService ibanResolverMockService; private final OwnerNameService ownerNameService; private final TransactionLinksService transactionLinksService; + private final FileManagementService fileManagementService; + private final Xs2aObjectMapper xs2aObjectMapper; + @Value("${xs2a.transaction.list.defaultPage}") private int defaultPage; @Value("${xs2a.transaction.list.defaultSize}") private int defaultSize; - - @Value("${test-download-transaction-list}") - private String transactionList; + @Value("${xs2a.transaction.list.filename:transactions.json}") + private String transactionsFilename; public AccountSpiImpl(AccountRestClient restClient, LedgersSpiAccountMapper accountMapper, AuthRequestInterceptor authRequestInterceptor, AspspConsentDataService consentDataService, FeignExceptionReader feignExceptionReader, IbanResolverMockService ibanResolverMockService, - OwnerNameService ownerNameService, TransactionLinksService transactionLinksService) { + OwnerNameService ownerNameService, TransactionLinksService transactionLinksService, + FileManagementService fileManagementService, Xs2aObjectMapper xs2aObjectMapper) { this.accountRestClient = restClient; this.accountMapper = accountMapper; this.authRequestInterceptor = authRequestInterceptor; @@ -104,6 +115,8 @@ public AccountSpiImpl(AccountRestClient restClient, LedgersSpiAccountMapper acco this.ibanResolverMockService = ibanResolverMockService; this.ownerNameService = ownerNameService; this.transactionLinksService = transactionLinksService; + this.fileManagementService = fileManagementService; + this.xs2aObjectMapper = xs2aObjectMapper; } @Override @@ -257,8 +270,16 @@ public SpiResponse requestTransactionsForAccount(@NotNull transactionsPaged.addAll(createStandingOrderReportMock()); } SpiTransactionLinks spiTransactionLinks = transactionLinksService.buildSpiTransactionLinks(page, size, transactionsOnPage); - SpiTransactionReport transactionReport = new SpiTransactionReport("downloadId", transactionsPaged, balances, - processAcceptMediaType(acceptMediaType), null, spiTransactionLinks, DEFAULT_TOTAL_PAGES); + + String downloadLink = getDownloadLink(transactionsPaged); + + SpiTransactionReport transactionReport = new SpiTransactionReport(downloadLink, + transactionsPaged, + balances, + processAcceptMediaType(acceptMediaType), + null, + spiTransactionLinks, + DEFAULT_TOTAL_PAGES); logger.info("Finally found {} transactions.", transactionReport.getTransactions().size()); aspspConsentDataProvider.updateAspspConsentData(consentDataService.store(response)); @@ -277,6 +298,17 @@ public SpiResponse requestTransactionsForAccount(@NotNull } } + private String getDownloadLink(List transactions) { + try { + byte[] bytes = xs2aObjectMapper.writeValueAsBytes(transactions); + Resource resource = new ByteArrayResource(bytes); + return fileManagementService.saveFileAndBuildDownloadLink(resource, transactionsFilename); + } catch (IOException ex) { + logger.error("Unable to save transactions file, Exception: {}, Message: {}", ex.getClass(), ex.getMessage()); + return StringUtils.EMPTY; + } + } + String processAcceptMediaType(String acceptMediaType) { return StringUtils.isBlank(acceptMediaType) || WILDCARD_ACCEPT_HEADER.equals(acceptMediaType) @@ -358,39 +390,53 @@ public SpiResponse requestTransactionsByDownloa @NotNull String downloadId, @NotNull SpiAspspConsentDataProvider aspspConsentDataProvider) { - byte[] aspspConsentData = aspspConsentDataProvider.loadAspspConsentData(); - + logger.info("Requested downloading list of transactions by download ID: {}", downloadId); try { - GlobalScaResponseTO response = applyAuthorisation(aspspConsentData); + Resource resource = fileManagementService.getFileByDownloadLink(downloadId); + if (!resource.isReadable()) { + logger.info("Reading transactions file is failed, file is not readable yet: consent ID: [{}], filename: [{}]", spiAccountConsent.getId(), resource.getFilename()); + return getErrorResponse("File is not ready, try again later", MessageErrorCode.RESOURCE_BLOCKED); + } - logger.info("Requested downloading list of transactions by download ID: {}", downloadId); - SpiTransactionsDownloadResponse transactionsDownloadResponse = getSpiTransactionsDownloadResponse(transactionList); + byte[] bytes = readResourceInputStreamBytes(resource); + int byteLength = bytes.length; - aspspConsentDataProvider.updateAspspConsentData(consentDataService.store(response)); + if (byteLength == 0) { + logger.error("Reading transactions input stream failed, file is empty: consent ID {}, filename {}", spiAccountConsent.getId(), resource.getFilename()); + return getErrorResponse("Nothing to download, file is empty", MessageErrorCode.RESOURCE_UNKNOWN_404); + } + logger.info("Consent ID {}, Decrypted Download id: [{}], Bytes read: [{}]", spiAccountConsent.getId(), downloadId, byteLength); + + SpiTransactionsDownloadResponse transactionsDownloadResponse = + new SpiTransactionsDownloadResponse(new ByteArrayInputStream(bytes), resource.getFilename(), byteLength); + + fileManagementService.deleteFileByDownloadLink(downloadId); return SpiResponse.builder() .payload(transactionsDownloadResponse) .build(); - } catch (FeignException feignException) { - String devMessage = feignExceptionReader.getErrorMessage(feignException); - logger.error("Request transactions by download link failed: consent ID {}, download link {}, devMessage {}", spiAccountConsent.getId(), downloadId, devMessage); - return SpiResponse.builder() - .error(buildTppMessage(feignException)) - .build(); - } finally { - authRequestInterceptor.setAccessToken(null); + } catch (FileManagementException fileManagementException) { + logger.error("Reading transactions file has failed (FileManagementException): consent ID: [{}], message: [{}]", spiAccountConsent.getId(), fileManagementException.getMessage()); + return getErrorResponse(fileManagementException.getMessage(), MessageErrorCode.RESOURCE_UNKNOWN_404); } } - private SpiTransactionsDownloadResponse getSpiTransactionsDownloadResponse(String transactionList) { - try (InputStream stream = new ByteArrayInputStream(transactionList.getBytes())) { - return new SpiTransactionsDownloadResponse(stream, "transactions.json", transactionList.getBytes().length); - } catch (IOException e) { - logger.error("It is not possible to prepare mock transaction list details"); - return null; + private byte[] readResourceInputStreamBytes(Resource resource) throws FileManagementException { + try (InputStream inputStream = resource.getInputStream()) { + return inputStream.readAllBytes(); + } catch (IOException ex) { + logger.error("Unable to read resource input stream bytes, Message: {}", ex.getMessage()); + throw new FileManagementException("File not found or corrupted(IOException). Message:" + ex.getMessage()); } } + private SpiResponse getErrorResponse(@Nullable String message, MessageErrorCode errorCode) { + TppMessage tppMessage = new TppMessage(errorCode, message); + return SpiResponse.builder() + .error(tppMessage) + .build(); + } + private List getSpiAccountDetails(boolean withBalance, @NotNull SpiAccountConsent accountConsent) { List accountDetailsList; if (isGlobalConsent(accountConsent.getAccess()) || isAllAvailableAccountsConsent(accountConsent)) { diff --git a/xs2a-connector/src/main/resources/mock-data.properties b/xs2a-connector/src/main/resources/mock-data.properties index 307218b4..b86be110 100644 --- a/xs2a-connector/src/main/resources/mock-data.properties +++ b/xs2a-connector/src/main/resources/mock-data.properties @@ -1,4 +1,4 @@ -test-download-transaction-list="transactions": {"booked": [{"transactionId": "tIrShGSFRAouD5-5pzFaQI","endToEndId": "20180625TAAA002","bookingDate": "2019-06-07","valueDate": "2019-06-07","transactionAmount": {"currency": "EUR","amount":"-100.00"},"creditorName": "Patrick Brueckner","creditorAccount": {"iban": "DE23760700240234367800","currency": "EUR"},"debtorAccount": {"iban": "DE80760700240271232400","currency": "EUR"},"remittanceInformationUnstructured": "Payment"},{"transactionId": "UTXbiUVpTEUg2fogrfXVmE","endToEndId": "20180625TAAA001","bookingDate": "2019-06-07","valueDate": "2019-06-07","transactionAmount": {"currency": "EUR","amount": "-150.00"},"creditorName": "Maria Brueckner","creditorAccount":{"iban": "DE67760700240243265400","currency": "EUR"},"debtorAccount": {"iban": "DE80760700240271232400","currency": "EUR"},"remittanceInformationUnstructured": "Payment"},{"transactionId": "dB4E-hTqR7MrDJo0vJYLR4","bookingDate": "2019-06-07","valueDate": "2019-06-07","transactionAmount": {"currency": "EUR","amount": "-2300.00"},"creditorName": "multiple","debtorAccount": {"iban": "DE80760700240271232400","currency": "EUR"}},{"transactionId":"N4zizaAzTyMnY9lnyFZfeM","bookingDate": "2019-06-07","valueDate": "2019-06-07","transactionAmount": {"currency": "EUR","amount": "-250.00"},"creditorName": "multiple","debtorAccount": {"iban": "DE80760700240271232400","currency": "EUR"}},{"transactionId": "ZlXZJMUuRU4h3J_NZ6cihw","endToEndId": "20180614T110000","bookingDate": "2019-06-07","valueDate": "2019-06-07","transactionAmount": {"currency": "EUR","amount": "2300.00"},"creditorName": "Anton Brueckner","creditorAccount": {"iban": "DE80760700240271232400","currency": "EUR"},"debtorAccount": {"iban": "DE38760700240320465700","currency": "EUR"},"remittanceInformationUnstructured": "Payment"},{"transactionId": "zI-AXA7_S3ss4d7wmHWGvc","endToEndId": "20180602T101000","bookingDate": "2019-06-07","valueDate": "2019-06-07","transactionAmount": {"currency": "EUR","amount": "-900.00"},"creditorName": "Rainer Maier","creditorAccount": {"iban":"DE84100100100568753108","currency": "EUR"},"debtorAccount": {"iban": "DE80760700240271232400","currency": "EUR"},"remittanceInformationUnstructured": "Payment"},{"transactionId": "XANyEaupTPIiEiunMHmz2I","endToEndId":"20180601T131000","bookingDate": "2019-06-07","valueDate": "2019-06-07","transactionAmount": {"currency": "EUR","amount": "-700.00"},"creditorName": "Max Musterman","creditorAccount": {"iban": "DE38760700240320465700","currency":"EUR"},"debtorAccount": {"iban": "DE80760700240271232400","currency": "EUR"},"remittanceInformationUnstructured": "Payment"}]} test-transaction-status-xml-body=45724572567256897269062017-02-14T20:24:56.021ZABCDDEFFDCBADEFFMIPI-123456789RI-123456789pain.001.001.032017-02-14T20:23:34.000Z1123ACCTBIPI-123456789RI-1234567891123ACCT xs2a.transaction.list.defaultPage=0 xs2a.transaction.list.defaultSize=100 +#xs2a.transaction.list.filename= diff --git a/xs2a-connector/src/test/java/de/adorsys/aspsp/xs2a/connector/spi/file/TransactionsFileManagementServiceSimpleTest.java b/xs2a-connector/src/test/java/de/adorsys/aspsp/xs2a/connector/spi/file/TransactionsFileManagementServiceSimpleTest.java new file mode 100644 index 00000000..b40ce572 --- /dev/null +++ b/xs2a-connector/src/test/java/de/adorsys/aspsp/xs2a/connector/spi/file/TransactionsFileManagementServiceSimpleTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2018-2021 adorsys GmbH & Co KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.adorsys.aspsp.xs2a.connector.spi.file; + +import de.adorsys.aspsp.xs2a.connector.spi.file.util.FileManagementService; +import de.adorsys.aspsp.xs2a.connector.spi.file.util.FileManagementServiceSimple; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.test.util.ReflectionTestUtils; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class TransactionsFileManagementServiceSimpleTest { + private final FileManagementService fileManagementService = new FileManagementServiceSimple(); + + @Test + void allMethodsFlow() throws IOException, InterruptedException { + ReflectionTestUtils.setField(fileManagementService, "configurationPath", "/tmp/XS2A"); + String inputFileName = "json/spi/impl/spi-transactions.json"; + String outputFileName = "transactions.json"; + String path = getClass().getClassLoader().getResource(inputFileName).getPath(); + Resource resource = new FileSystemResource(path); + byte[] bytes = resource.getInputStream().readAllBytes(); + + //Save file and build download link + String downloadLink = fileManagementService.saveFileAndBuildDownloadLink(resource, outputFileName); + assertNotNull(downloadLink); + //Delay to ensure the file is written + Thread.sleep(100); + + //Get file by download link + Resource fileByDownloadLink = fileManagementService.getFileByDownloadLink(downloadLink); + assertEquals(outputFileName, fileByDownloadLink.getFilename()); + assertArrayEquals(bytes, fileByDownloadLink.getInputStream().readAllBytes()); + + //Delete file success (file exist) + Assertions.assertDoesNotThrow(() -> fileManagementService.deleteFileByDownloadLink(downloadLink)); + } +} diff --git a/xs2a-connector/src/test/java/de/adorsys/aspsp/xs2a/connector/spi/impl/AccountSpiImplTest.java b/xs2a-connector/src/test/java/de/adorsys/aspsp/xs2a/connector/spi/impl/AccountSpiImplTest.java index 13e0c7a8..fb28b622 100644 --- a/xs2a-connector/src/test/java/de/adorsys/aspsp/xs2a/connector/spi/impl/AccountSpiImplTest.java +++ b/xs2a-connector/src/test/java/de/adorsys/aspsp/xs2a/connector/spi/impl/AccountSpiImplTest.java @@ -1,9 +1,12 @@ package de.adorsys.aspsp.xs2a.connector.spi.impl; +import com.fasterxml.jackson.core.JsonProcessingException; import de.adorsys.aspsp.xs2a.connector.account.IbanAccountReference; import de.adorsys.aspsp.xs2a.connector.account.OwnerNameService; import de.adorsys.aspsp.xs2a.connector.spi.converter.LedgersSpiAccountMapper; import de.adorsys.aspsp.xs2a.connector.spi.converter.LedgersSpiAccountMapperImpl; +import de.adorsys.aspsp.xs2a.connector.spi.file.exception.FileManagementException; +import de.adorsys.aspsp.xs2a.connector.spi.file.util.FileManagementService; import de.adorsys.aspsp.xs2a.connector.spi.impl.service.TransactionLinksService; import de.adorsys.aspsp.xs2a.util.JsonReader; import de.adorsys.aspsp.xs2a.util.TestSpiDataProvider; @@ -15,8 +18,10 @@ import de.adorsys.ledgers.rest.client.AccountRestClient; import de.adorsys.ledgers.rest.client.AuthRequestInterceptor; import de.adorsys.ledgers.util.domain.CustomPageImpl; +import de.adorsys.psd2.mapper.Xs2aObjectMapper; import de.adorsys.psd2.xs2a.core.ais.BookingStatus; import de.adorsys.psd2.xs2a.core.consent.AspspConsentData; +import de.adorsys.psd2.xs2a.core.error.MessageErrorCode; import de.adorsys.psd2.xs2a.spi.domain.SpiAspspConsentDataProvider; import de.adorsys.psd2.xs2a.spi.domain.SpiContextData; import de.adorsys.psd2.xs2a.spi.domain.account.*; @@ -33,10 +38,12 @@ import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.FileSystemResource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import java.lang.reflect.Field; +import java.io.File; import java.nio.charset.Charset; import java.time.LocalDate; import java.util.Arrays; @@ -58,6 +65,8 @@ class AccountSpiImplTest { private static final AspspConsentData ASPSP_CONSENT_DATA = new AspspConsentData(BYTES, CONSENT_ID); private static final String RESOURCE_ID = "11111-999999999"; private static final String RESOURCE_ID_SECOND_ACCOUNT = "11111-999999998"; + private static final String TRANSACTIONS_FILEPATH = "/tmp/XS2A/1233444/transactions.json"; + private static final String EMPTY_FILE_MESSAGE = "Nothing to download, file is empty"; private final static LocalDate DATE_FROM = LocalDate.of(2019, 1, 1); private final static LocalDate DATE_TO = LocalDate.of(2020, 1, 1); @@ -94,6 +103,10 @@ class AccountSpiImplTest { private OwnerNameService ownerNameService; @Mock private TransactionLinksService transactionLinksService; + @Mock + private Xs2aObjectMapper xs2aObjectMapper; + @Mock + FileManagementService fileManagementService; private final JsonReader jsonReader = new JsonReader(); private SpiAccountConsent spiAccountConsent; @@ -116,7 +129,7 @@ void setUp() { } @Test - void requestTransactionsForAccount_success() { + void requestTransactionsForAccount_success() throws JsonProcessingException, FileManagementException { BearerTokenTO bearerTokenTO = new BearerTokenTO(); bearerTokenTO.setAccess_token("access_token"); when(scaResponseTO.getBearerToken()).thenReturn(bearerTokenTO); @@ -125,6 +138,8 @@ void requestTransactionsForAccount_success() { when(accountRestClient.getTransactionByDatesPaged(RESOURCE_ID, DATE_FROM, DATE_TO, PAGE, SIZE)).thenReturn(ResponseEntity.ok(new CustomPageImpl<>())); when(accountRestClient.getBalances(RESOURCE_ID)).thenReturn(ResponseEntity.ok(Collections.emptyList())); when(aspspConsentDataProvider.loadAspspConsentData()).thenReturn(BYTES); + when(xs2aObjectMapper.writeValueAsBytes(anyList())).thenReturn(BYTES); + when(fileManagementService.saveFileAndBuildDownloadLink(new ByteArrayResource(BYTES), null)).thenReturn(TRANSACTIONS_FILEPATH); SpiResponse actualResponse = accountSpi.requestTransactionsForAccount(SPI_CONTEXT_DATA, buildSpiTransactionReportParameters(MediaType.APPLICATION_XML_VALUE), accountReference, spiAccountConsent, aspspConsentDataProvider); @@ -154,7 +169,7 @@ void requestTransactionsForAccount_InformationBookingStatus() { } @Test - void requestTransactionsForAccount_useDefaultAcceptTypeWhenNull_success() { + void requestTransactionsForAccount_useDefaultAcceptTypeWhenNull_success() throws JsonProcessingException, FileManagementException { BearerTokenTO bearerTokenTO = new BearerTokenTO(); bearerTokenTO.setAccess_token("access_token"); when(aspspConsentDataProvider.loadAspspConsentData()).thenReturn(BYTES); @@ -163,6 +178,9 @@ void requestTransactionsForAccount_useDefaultAcceptTypeWhenNull_success() { when(tokenService.store(scaResponseTO)).thenReturn(BYTES); when(accountRestClient.getTransactionByDatesPaged(RESOURCE_ID, DATE_FROM, DATE_TO, PAGE, SIZE)).thenReturn(ResponseEntity.ok(new CustomPageImpl<>())); when(accountRestClient.getBalances(RESOURCE_ID)).thenReturn(ResponseEntity.ok(Collections.emptyList())); + when(xs2aObjectMapper.writeValueAsBytes(anyList())).thenReturn(BYTES); + when(fileManagementService.saveFileAndBuildDownloadLink(new ByteArrayResource(BYTES), null)).thenReturn(TRANSACTIONS_FILEPATH); + SpiResponse actualResponse = accountSpi.requestTransactionsForAccount(SPI_CONTEXT_DATA, buildSpiTransactionReportParameters(null), accountReference, spiAccountConsent, aspspConsentDataProvider); @@ -177,7 +195,7 @@ void requestTransactionsForAccount_useDefaultAcceptTypeWhenNull_success() { } @Test - void requestTransactionsForAccount_useDefaultAcceptTypeWhenWildcard_success() { + void requestTransactionsForAccount_useDefaultAcceptTypeWhenWildcard_success() throws JsonProcessingException, FileManagementException { BearerTokenTO bearerTokenTO = new BearerTokenTO(); bearerTokenTO.setAccess_token("access_token"); when(aspspConsentDataProvider.loadAspspConsentData()).thenReturn(BYTES); @@ -186,6 +204,8 @@ void requestTransactionsForAccount_useDefaultAcceptTypeWhenWildcard_success() { when(tokenService.store(scaResponseTO)).thenReturn(BYTES); when(accountRestClient.getTransactionByDatesPaged(RESOURCE_ID, DATE_FROM, DATE_TO, PAGE, SIZE)).thenReturn(ResponseEntity.ok(new CustomPageImpl<>())); when(accountRestClient.getBalances(RESOURCE_ID)).thenReturn(ResponseEntity.ok(Collections.emptyList())); + when(xs2aObjectMapper.writeValueAsBytes(anyList())).thenReturn(BYTES); + when(fileManagementService.saveFileAndBuildDownloadLink(new ByteArrayResource(BYTES), null)).thenReturn(TRANSACTIONS_FILEPATH); SpiResponse actualResponse = accountSpi.requestTransactionsForAccount(SPI_CONTEXT_DATA, buildSpiTransactionReportParameters("*/*"), accountReference, spiAccountConsent, aspspConsentDataProvider); @@ -225,6 +245,33 @@ void requestTransactionsForAccount_withException() { verify(tokenService, times(1)).store(scaResponseTO); } + @Test + void requestTransactionsForAccount_emptyDownloadLink() throws JsonProcessingException, FileManagementException { + BearerTokenTO bearerTokenTO = new BearerTokenTO(); + bearerTokenTO.setAccess_token("access_token"); + when(aspspConsentDataProvider.loadAspspConsentData()).thenReturn(BYTES); + when(scaResponseTO.getBearerToken()).thenReturn(bearerTokenTO); + when(tokenService.response(BYTES)).thenReturn(scaResponseTO); + when(tokenService.store(scaResponseTO)).thenReturn(BYTES); + when(accountRestClient.getTransactionByDatesPaged(RESOURCE_ID, DATE_FROM, DATE_TO, PAGE, SIZE)).thenReturn(ResponseEntity.ok(new CustomPageImpl<>())); + when(accountRestClient.getBalances(RESOURCE_ID)).thenReturn(ResponseEntity.ok(Collections.emptyList())); + when(xs2aObjectMapper.writeValueAsBytes(anyList())).thenReturn(BYTES); + + when(fileManagementService.saveFileAndBuildDownloadLink(new ByteArrayResource(BYTES), null)).thenThrow(getFileManagementException("FileManagementException")); + + SpiResponse actualResponse = accountSpi.requestTransactionsForAccount(SPI_CONTEXT_DATA, buildSpiTransactionReportParameters(MediaType.APPLICATION_XML_VALUE), + accountReference, spiAccountConsent, aspspConsentDataProvider); + + assertTrue(actualResponse.getErrors().isEmpty()); + assertTrue(actualResponse.getPayload().getDownloadId().isEmpty()); + verify(aspspConsentDataProvider, times(2)).loadAspspConsentData(); + verify(tokenService, times(2)).response(BYTES); + verify(authRequestInterceptor, times(2)).setAccessToken(scaResponseTO.getBearerToken().getAccess_token()); + verify(authRequestInterceptor, times(2)).setAccessToken(null); + verify(accountRestClient, times(1)).getTransactionByDatesPaged(RESOURCE_ID, DATE_FROM, DATE_TO, PAGE, SIZE); + verify(tokenService, times(2)).store(scaResponseTO); + } + @Test void requestAccountList_withoutBalance_regularConsent() { BearerTokenTO bearerTokenTO = new BearerTokenTO(); @@ -721,40 +768,58 @@ void requestTransactionForAccountByTransactionId_WithException() { } @Test - void requestTransactionsByDownloadLink_success() throws NoSuchFieldException, IllegalAccessException { - BearerTokenTO bearerTokenTO = new BearerTokenTO(); - bearerTokenTO.setAccess_token("access_token"); - when(aspspConsentDataProvider.loadAspspConsentData()).thenReturn(BYTES); - when(scaResponseTO.getBearerToken()).thenReturn(bearerTokenTO); - when(tokenService.response(BYTES)).thenReturn(scaResponseTO); - when(tokenService.store(scaResponseTO)).thenReturn(BYTES); + void requestTransactionsByDownloadLink_success() throws FileManagementException { + when(fileManagementService.getFileByDownloadLink(DOWNLOAD_ID)).thenReturn(new ByteArrayResource(BYTES)); - String transactionList = "transactionList"; - Field fieldTransactionList = accountSpi.getClass().getDeclaredField(transactionList); - fieldTransactionList.setAccessible(true); - fieldTransactionList.set(accountSpi, transactionList); - - SpiResponse actualResponse = accountSpi - .requestTransactionsByDownloadLink(SPI_CONTEXT_DATA, spiAccountConsent, DOWNLOAD_ID, aspspConsentDataProvider); + SpiResponse actualResponse = + accountSpi.requestTransactionsByDownloadLink(SPI_CONTEXT_DATA, spiAccountConsent, DOWNLOAD_ID, aspspConsentDataProvider); assertTrue(actualResponse.getErrors().isEmpty()); assertNotNull(actualResponse.getPayload()); - verifyApplyAuthorisationUsedAndInterceptorWithNull(); + verify(fileManagementService, times(1)).deleteFileByDownloadLink(DOWNLOAD_ID); } @Test - void requestTransactionsByDownloadLink_WithError() { - when(aspspConsentDataProvider.loadAspspConsentData()).thenReturn(BYTES); - when(tokenService.response(BYTES)).thenThrow(getFeignException()); + void requestTransactionsByDownloadLink_blockedResource() throws FileManagementException { + File file = new File(TRANSACTIONS_FILEPATH); + file.setReadable(false); + FileSystemResource resource = new FileSystemResource(file); + when(fileManagementService.getFileByDownloadLink(DOWNLOAD_ID)).thenReturn(resource); - SpiResponse actualResponse = accountSpi - .requestTransactionsByDownloadLink(SPI_CONTEXT_DATA, spiAccountConsent, DOWNLOAD_ID, aspspConsentDataProvider); + SpiResponse actualResponse = + accountSpi.requestTransactionsByDownloadLink(SPI_CONTEXT_DATA, spiAccountConsent, DOWNLOAD_ID, aspspConsentDataProvider); assertFalse(actualResponse.getErrors().isEmpty()); assertNull(actualResponse.getPayload()); - verify(aspspConsentDataProvider, times(1)).loadAspspConsentData(); - verify(tokenService, times(1)).response(BYTES); - verify(authRequestInterceptor, times(1)).setAccessToken(null); + assertEquals(MessageErrorCode.RESOURCE_BLOCKED, actualResponse.getErrors().get(0).getErrorCode()); + verify(fileManagementService, never()).deleteFileByDownloadLink(DOWNLOAD_ID); + } + + @Test + void requestTransactionsByDownloadLink_emptyFile() throws FileManagementException { + ByteArrayResource resource = new ByteArrayResource("".getBytes()); + when(fileManagementService.getFileByDownloadLink(DOWNLOAD_ID)).thenReturn(resource); + + SpiResponse actualResponse = + accountSpi.requestTransactionsByDownloadLink(SPI_CONTEXT_DATA, spiAccountConsent, DOWNLOAD_ID, aspspConsentDataProvider); + + assertFalse(actualResponse.getErrors().isEmpty()); + assertNull(actualResponse.getPayload()); + assertEquals(MessageErrorCode.RESOURCE_UNKNOWN_404, actualResponse.getErrors().get(0).getErrorCode()); + assertEquals(EMPTY_FILE_MESSAGE, actualResponse.getErrors().get(0).getMessageText()); + verify(fileManagementService, never()).deleteFileByDownloadLink(DOWNLOAD_ID); + } + + @Test + void requestTransactionsByDownloadLink_WithError() throws FileManagementException { + when(fileManagementService.getFileByDownloadLink(DOWNLOAD_ID)).thenThrow(getFileManagementException(null)); + + SpiResponse actualResponse = + accountSpi.requestTransactionsByDownloadLink(SPI_CONTEXT_DATA, spiAccountConsent, DOWNLOAD_ID, aspspConsentDataProvider); + + assertFalse(actualResponse.getErrors().isEmpty()); + assertNull(actualResponse.getPayload()); + verify(fileManagementService, never()).deleteFileByDownloadLink(DOWNLOAD_ID); } @Test @@ -784,6 +849,10 @@ private FeignException getFeignException() { buildErrorResponse()); } + private FileManagementException getFileManagementException(String message) { + return new FileManagementException(message); + } + private Response buildErrorResponse() { return Response.builder() .status(404) diff --git a/xs2a-connector/src/test/resources/json/spi/impl/spi-transactions.json b/xs2a-connector/src/test/resources/json/spi/impl/spi-transactions.json new file mode 100644 index 00000000..e01d935d --- /dev/null +++ b/xs2a-connector/src/test/resources/json/spi/impl/spi-transactions.json @@ -0,0 +1 @@ +[{"transactionId":"1234567","entryReference":"12345678","endToEndId":"123456789","mandateId":"12345","checkId":"1234567","creditorId":"12345","bookingDate":"2017-01-01","valueDate":"2018-01-01","spiAmount":{"currency":"EUR","amount":1.06},"exchangeRate":[{"sourceCurrency":"EUR","exchangeRate":"12","unitCurrency":"UAH","targetCurrency":"USD","quotationDate":"2019-05-28","contractIdentification":"contractIdentification"}],"spiTransactionInfo":{"creditorName":"John Miles","creditorAccount":{"aspspAccountId":"DE52500105173911841934","resourceId":"DE52500105173911841934","iban":"DE52500105173911841934","bban":"Test BBAN","pan":"1111","maskedPan":"23456xxxxxx1234","msisdn":"0172/1111111","currency":"EUR","otherAccountIdentification":null},"creditorAgent":"creditor agent","ultimateCreditor":"Paul Simpson","debtorName":"Jan","debtorAccount":{"aspspAccountId":"DE52500105173911841934","resourceId":"DE52500105173911841934","iban":"DE52500105173911841934","bban":"Test BBAN","pan":"1111","maskedPan":"23456xxxxxx1234","msisdn":"0172/1111111","currency":"EUR","otherAccountIdentification":null},"debtorAgent":"debtor agent","ultimateDebtor":"Max","remittanceInformationUnstructured":"Ref Number Merchant","remittanceInformationUnstructuredArray":["mock remittance unstructured array"],"remittanceInformationStructured":"Ref Number Merchant","remittanceInformationStructuredArray":["mock remittance reference"],"purposeCode":"CDCS"},"bankTransactionCodeCode":"DE52500105173911841934","proprietaryBankTransactionCode":"DE52500105173911841934","additionalInformation":"mock additional information","additionalInformationStructured":null,"balanceAfterTransaction":{"spiBalanceAmount":{"currency":"EUR","amount":1000},"spiBalanceType":"INTERIM_AVAILABLE","lastChangeDateTime":"2018-03-31T15:16:16.372","referenceDate":"2018-03-31","lastCommittedTransaction":"lastCommittedTransaction","creditLimitIncluded":null},"batchIndicator":false,"batchNumberOfTransactions":14,"entryDetails":[{"endToEndId":"endToEndId","mandateId":"mandateId","checkId":"checkId","creditorId":"creditorId","transactionAmount":{"currency":"EUR","amount":123},"currencyExchange":[{"sourceCurrency":"USD","exchangeRate":"23","unitCurrency":"EUR","targetCurrency":"EUR","quotationDate":"2018-02-02","contractIdentification":"contractIdentification"}],"spiTransactionInfo":{"creditorName":"creditorName","creditorAccount":{"aspspAccountId":"1234567890","resourceId":"3276159a-27ad-46db-8491-71e629d82baa","iban":"DE52500105173911841934","bban":"Test BBAN","pan":"1111","maskedPan":"23456xxxxxx1234","msisdn":"0172/1111111","currency":"EUR","otherAccountIdentification":null},"creditorAgent":"creditorAgent","ultimateCreditor":"ultimateCreditor","debtorName":"debtorName","debtorAccount":{"aspspAccountId":"111234567890","resourceId":"3276159a-27ad-46db-8491-71e629d82baa","iban":"DE52500105173911841934","bban":"Test BBAN","pan":"2222","maskedPan":"23456xxxxxx1234","msisdn":"0172/1111111","currency":"USD","otherAccountIdentification":null},"debtorAgent":"debtorAgent","ultimateDebtor":"ultimateDebtor","remittanceInformationUnstructured":"remittanceInformationUnstructured","remittanceInformationUnstructuredArray":["remittanceInformationUnstructuredArray"],"remittanceInformationStructured":"remittanceInformationStructured","remittanceInformationStructuredArray":["remittanceInformationStructuredArray"],"purposeCode":"CDCB"}}]}] From e79d57e6265284713f6e76638dd682bc72c6f338 Mon Sep 17 00:00:00 2001 From: semenykhin Date: Mon, 20 Dec 2021 17:10:36 +0200 Subject: [PATCH 4/5] prepare for release 13.4 --- README.md | 2 ++ doc/release_notes/Release_notes_13.4.adoc | 20 ++++++++++++++++ doc/release_notes/Release_notes_13.4_SSE.adoc | 23 ------------------- pom.xml | 2 +- 4 files changed, 23 insertions(+), 24 deletions(-) delete mode 100644 doc/release_notes/Release_notes_13.4_SSE.adoc diff --git a/README.md b/README.md index e2271430..4e710a41 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,12 @@ Please use correct version of xs2a-connector-examples and Ledgers. Matches are d | xs2a-connector-examples | Ledgers | |-------------------------|---------| +| v.13.4 | v.4.17 | | v.13.3 | v.4.17 | | v.13.2 | v.4.16 | | v.13.1 | v.4.15 | | v.13.0 | v.4.14 | +| v.12.4 | v.4.17 | | v.12.3 | v.4.17 | | v.12.2 | v.4.16 | | v.12.1 | v.4.15 | diff --git a/doc/release_notes/Release_notes_13.4.adoc b/doc/release_notes/Release_notes_13.4.adoc index e7349939..0b5e5f33 100644 --- a/doc/release_notes/Release_notes_13.4.adoc +++ b/doc/release_notes/Release_notes_13.4.adoc @@ -1,3 +1,23 @@ = Release notes v.13.4 == Table of Contents + +* Implemented writing transactions data into file asynchronous + +* Technical password from property `xs2a.funds-confirmation-user-password` changed + +== Implemented writing transactions data into file asynchronous + +From now on, content of downloaded transaction file is no more mocked, but the real data got during Read Transaction List +request. Transaction file writing is being performed in a separate thread to increase performance and reduce response time. +New properties were added into `application.yml` : + +* `xs2a.download.files.dir` - path to directory, where files are being created for downloading +* `xs2a.download.files.cleanup.delay_s` - time in seconds, specifies how long download link will be valid after the first retrieval request. +When specified time passes, file and its parent directory will be deleted, all next requests by the same download +link will cause response with code 404 `Not Found` + +== Technical password from property `xs2a.funds-confirmation-user-password` changed + +Property value `xs2a.funds-confirmation-user-password` changed from `12345` to `admin123` to handle properly +ASPSP PIIS consents. diff --git a/doc/release_notes/Release_notes_13.4_SSE.adoc b/doc/release_notes/Release_notes_13.4_SSE.adoc deleted file mode 100644 index 5bc35d27..00000000 --- a/doc/release_notes/Release_notes_13.4_SSE.adoc +++ /dev/null @@ -1,23 +0,0 @@ -= Release notes v.13.4 - -== Table of Contents - -* Implemented writing transactions data into file asynchronous - -* Technical password from property `xs2a.funds-confirmation-user-password` changed - -== Implemented writing transactions data into file asynchronous - -From now on, content of downloaded transaction file is no more mocked, but the real data got during Read Transaction List -request. Transaction file writing is being performed in a separate thread to increase performance and reduce response time. -New properties were added into `application.yml` : - -* `xs2a.download.files.dir` - path to directory, where files are being created for downloading -* `xs2a.download.files.cleanup.delay_s` - time in seconds, specifies how long download link will be valid after the first retrieval request. -When specified time passes, file and its parent directory will be deleted, all next requests by the same download -link will cause response with code 400 `Resource Blocked` - -== Technical password from property `xs2a.funds-confirmation-user-password` changed - -Property value `xs2a.funds-confirmation-user-password` changed from `12345` to `admin123` to handle properly -ASPSP PIIS consents. diff --git a/pom.xml b/pom.xml index 771ded7f..cf9022db 100755 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,7 @@ ${project.basedir} - 13.4-SNAPSHOT + 13.4 4.17 From d569376e402e4c4e155cb8ff73f45c9c6751451b Mon Sep 17 00:00:00 2001 From: semenykhin Date: Mon, 20 Dec 2021 17:19:51 +0200 Subject: [PATCH 5/5] Prepare release 13.4 --- gateway-app-embedded/pom.xml | 2 +- gateway-app/pom.xml | 2 +- ledgers-rest-client/pom.xml | 2 +- pom.xml | 2 +- xs2a-connector-embedded/pom.xml | 2 +- xs2a-connector-oauth-service/pom.xml | 2 +- xs2a-connector-remote/pom.xml | 2 +- xs2a-connector/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gateway-app-embedded/pom.xml b/gateway-app-embedded/pom.xml index a7f1ca7b..7a596168 100644 --- a/gateway-app-embedded/pom.xml +++ b/gateway-app-embedded/pom.xml @@ -21,7 +21,7 @@ xs2a-connector-examples de.adorsys.ledgers - 13.4-SNAPSHOT + 13.4 4.0.0 diff --git a/gateway-app/pom.xml b/gateway-app/pom.xml index 53e80629..90081cb3 100644 --- a/gateway-app/pom.xml +++ b/gateway-app/pom.xml @@ -5,7 +5,7 @@ de.adorsys.ledgers xs2a-connector-examples - 13.4-SNAPSHOT + 13.4 .. gateway-app diff --git a/ledgers-rest-client/pom.xml b/ledgers-rest-client/pom.xml index cf8e4284..cdfbc4e6 100644 --- a/ledgers-rest-client/pom.xml +++ b/ledgers-rest-client/pom.xml @@ -5,7 +5,7 @@ de.adorsys.ledgers xs2a-connector-examples - 13.4-SNAPSHOT + 13.4 .. ledgers-rest-client diff --git a/pom.xml b/pom.xml index cf9022db..4bc810be 100755 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 4.0.0 de.adorsys.ledgers xs2a-connector-examples - 13.4-SNAPSHOT + 13.4 pom XS2A Connector Examples diff --git a/xs2a-connector-embedded/pom.xml b/xs2a-connector-embedded/pom.xml index b8e20d88..1e7ab0de 100644 --- a/xs2a-connector-embedded/pom.xml +++ b/xs2a-connector-embedded/pom.xml @@ -21,7 +21,7 @@ xs2a-connector-examples de.adorsys.ledgers - 13.4-SNAPSHOT + 13.4 4.0.0 diff --git a/xs2a-connector-oauth-service/pom.xml b/xs2a-connector-oauth-service/pom.xml index 4af819e3..7e1c5b5f 100644 --- a/xs2a-connector-oauth-service/pom.xml +++ b/xs2a-connector-oauth-service/pom.xml @@ -21,7 +21,7 @@ xs2a-connector-examples de.adorsys.ledgers - 13.4-SNAPSHOT + 13.4 4.0.0 diff --git a/xs2a-connector-remote/pom.xml b/xs2a-connector-remote/pom.xml index 441e66bb..38a8b73e 100644 --- a/xs2a-connector-remote/pom.xml +++ b/xs2a-connector-remote/pom.xml @@ -5,7 +5,7 @@ de.adorsys.ledgers xs2a-connector-examples - 13.4-SNAPSHOT + 13.4 .. diff --git a/xs2a-connector/pom.xml b/xs2a-connector/pom.xml index a252f155..e2bb1aa2 100644 --- a/xs2a-connector/pom.xml +++ b/xs2a-connector/pom.xml @@ -5,7 +5,7 @@ de.adorsys.ledgers xs2a-connector-examples - 13.4-SNAPSHOT + 13.4 ..