This is an automated email from the ASF dual-hosted git repository. arnold pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit 232a954093545a15da4ff5e2d8a5859ddbe35e75 Author: Arnold Galovics <[email protected]> AuthorDate: Mon Jul 3 17:56:58 2023 +0200 Fixes for dep upgrades --- .github/workflows/build-docker-mariadb.yml | 2 +- .github/workflows/build-docker-postgresql.yml | 2 +- .../service/tenant/JdbcTenantDetailsService.java | 5 ++ .../jobs/service/SchedulerTriggerListener.java | 32 ++---------- ...erTriggerListener.java => SchedulerVetoer.java} | 45 +++------------- .../PostInterestForSavingConfig.java | 4 +- .../PostInterestForSavingTasklet.java | 14 ++--- ...countWritePlatformServiceJpaRepositoryImpl.java | 6 +-- .../service/SavingsSchedularInterestPoster.java | 19 ++----- .../SavingsSchedularInterestPosterTask.java | 60 ++++++++++++++++++++++ 10 files changed, 92 insertions(+), 97 deletions(-) diff --git a/.github/workflows/build-docker-mariadb.yml b/.github/workflows/build-docker-mariadb.yml index 6c4c79f24..dc5cf4c64 100644 --- a/.github/workflows/build-docker-mariadb.yml +++ b/.github/workflows/build-docker-mariadb.yml @@ -26,6 +26,6 @@ jobs: - name: Wait for stack to come up run: sleep 120 - name: Check health - run: curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health + run: curl -f -k --retry 10 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health - name: Check info run: (( $(curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/info | wc --chars) > 100 )) diff --git a/.github/workflows/build-docker-postgresql.yml b/.github/workflows/build-docker-postgresql.yml index b934ec110..87e89f59c 100644 --- a/.github/workflows/build-docker-postgresql.yml +++ b/.github/workflows/build-docker-postgresql.yml @@ -26,6 +26,6 @@ jobs: - name: Wait for stack to come up run: sleep 120 - name: Check health - run: curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health + run: curl -f -k --retry 10 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health - name: Check info run: (( $(curl -f -k --retry 5 --retry-connrefused --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/info | wc --chars) > 100 )) diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/tenant/JdbcTenantDetailsService.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/tenant/JdbcTenantDetailsService.java index 6ff01e39f..3cc6167ed 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/tenant/JdbcTenantDetailsService.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/tenant/JdbcTenantDetailsService.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.infrastructure.core.service.tenant; +import static org.apache.commons.lang3.StringUtils.isBlank; + import java.util.List; import javax.sql.DataSource; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; @@ -46,6 +48,9 @@ public class JdbcTenantDetailsService implements TenantDetailsService { @Override @Cacheable(value = "tenantsById") public FineractPlatformTenant loadTenantById(final String tenantIdentifier) { + if (isBlank(tenantIdentifier)) { + throw new IllegalArgumentException("tenantIdentifier cannot be blank"); + } try { final TenantMapper rm = new TenantMapper(false); final String sql = "select " + rm.schema() + " where t.identifier = ?"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java index e0e9f920a..a70fbcae9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java @@ -18,34 +18,25 @@ */ package org.apache.fineract.infrastructure.jobs.service; -import java.time.LocalDate; -import java.util.HashMap; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; -import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService; import org.apache.fineract.infrastructure.core.domain.ActionContext; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService; import org.quartz.JobExecutionContext; -import org.quartz.JobKey; import org.quartz.Trigger; import org.quartz.Trigger.CompletedExecutionInstruction; import org.quartz.TriggerListener; import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Isolation; -import org.springframework.transaction.annotation.Transactional; @Slf4j @RequiredArgsConstructor @Component public class SchedulerTriggerListener implements TriggerListener { - private final SchedularWritePlatformService schedularService; private final TenantDetailsService tenantDetailsService; - - private final BusinessDateReadPlatformService businessDateReadPlatformService; + private final SchedulerVetoer schedulerVetoer; @Override public String getName() { @@ -58,27 +49,12 @@ public class SchedulerTriggerListener implements TriggerListener { } @Override - @Transactional(isolation = Isolation.READ_COMMITTED) public boolean vetoJobExecution(final Trigger trigger, final JobExecutionContext context) { - final String tenantIdentifier = trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER); - final FineractPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantIdentifier); + String tenantIdentifier = trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER); + FineractPlatformTenant tenant = tenantDetailsService.loadTenantById(tenantIdentifier); ThreadLocalContextUtil.setTenant(tenant); ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); - HashMap<BusinessDateType, LocalDate> businessDates = businessDateReadPlatformService.getBusinessDates(); - ThreadLocalContextUtil.setBusinessDates(businessDates); - final JobKey key = trigger.getJobKey(); - final String jobKey = key.getName() + SchedulerServiceConstants.JOB_KEY_SEPERATOR + key.getGroup(); - String triggerType = SchedulerServiceConstants.TRIGGER_TYPE_CRON; - if (context.getMergedJobDataMap().containsKey(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE)) { - triggerType = context.getMergedJobDataMap().getString(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE); - } - boolean vetoJob = this.schedularService.processJobDetailForExecution(jobKey, triggerType); - if (vetoJob) { - log.warn( - "vetoJobExecution() WILL veto the execution (returning vetoJob == true; the job's execute method will NOT be called); tenant={}, jobKey={}, triggerType={}, trigger={}, context={}", - tenantIdentifier, jobKey, triggerType, trigger, context); - } - return vetoJob; + return schedulerVetoer.veto(trigger, context); } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerVetoer.java similarity index 58% copy from fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerVetoer.java index e0e9f920a..2481d9f4b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerVetoer.java @@ -24,55 +24,34 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService; -import org.apache.fineract.infrastructure.core.domain.ActionContext; -import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; -import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService; import org.quartz.JobExecutionContext; import org.quartz.JobKey; import org.quartz.Trigger; -import org.quartz.Trigger.CompletedExecutionInstruction; -import org.quartz.TriggerListener; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; @Slf4j -@RequiredArgsConstructor @Component -public class SchedulerTriggerListener implements TriggerListener { +@RequiredArgsConstructor +public class SchedulerVetoer { private final SchedularWritePlatformService schedularService; - private final TenantDetailsService tenantDetailsService; - private final BusinessDateReadPlatformService businessDateReadPlatformService; - @Override - public String getName() { - return "Fineract Global Scheduler Trigger Listener"; - } - - @Override - public void triggerFired(Trigger trigger, JobExecutionContext context) { - log.debug("triggerFired() trigger={}, context={}", trigger, context); - } - - @Override @Transactional(isolation = Isolation.READ_COMMITTED) - public boolean vetoJobExecution(final Trigger trigger, final JobExecutionContext context) { - final String tenantIdentifier = trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER); - final FineractPlatformTenant tenant = this.tenantDetailsService.loadTenantById(tenantIdentifier); - ThreadLocalContextUtil.setTenant(tenant); - ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); + public boolean veto(Trigger trigger, JobExecutionContext context) { + String tenantIdentifier = trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER); HashMap<BusinessDateType, LocalDate> businessDates = businessDateReadPlatformService.getBusinessDates(); ThreadLocalContextUtil.setBusinessDates(businessDates); - final JobKey key = trigger.getJobKey(); - final String jobKey = key.getName() + SchedulerServiceConstants.JOB_KEY_SEPERATOR + key.getGroup(); + JobKey key = trigger.getJobKey(); + String jobKey = key.getName() + SchedulerServiceConstants.JOB_KEY_SEPERATOR + key.getGroup(); String triggerType = SchedulerServiceConstants.TRIGGER_TYPE_CRON; if (context.getMergedJobDataMap().containsKey(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE)) { triggerType = context.getMergedJobDataMap().getString(SchedulerServiceConstants.TRIGGER_TYPE_REFERENCE); } - boolean vetoJob = this.schedularService.processJobDetailForExecution(jobKey, triggerType); + boolean vetoJob = schedularService.processJobDetailForExecution(jobKey, triggerType); if (vetoJob) { log.warn( "vetoJobExecution() WILL veto the execution (returning vetoJob == true; the job's execute method will NOT be called); tenant={}, jobKey={}, triggerType={}, trigger={}, context={}", @@ -80,14 +59,4 @@ public class SchedulerTriggerListener implements TriggerListener { } return vetoJob; } - - @Override - public void triggerMisfired(final Trigger trigger) { - log.error("triggerMisfired() trigger={}", trigger); - } - - @Override - public void triggerComplete(Trigger trigger, JobExecutionContext context, CompletedExecutionInstruction triggerInstructionCode) { - log.debug("triggerComplete() trigger={}, context={}, completedExecutionInstruction={}", trigger, context, triggerInstructionCode); - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingConfig.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingConfig.java index 0f121cc1f..b82dd2397 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingConfig.java @@ -24,7 +24,7 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper; import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; -import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPoster; +import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPosterTask; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.job.builder.JobBuilder; @@ -50,7 +50,7 @@ public class PostInterestForSavingConfig { @Autowired private ConfigurationDomainService configurationDomainService; @Autowired - private SavingsSchedularInterestPoster savingsSchedularInterestPoster; + private SavingsSchedularInterestPosterTask savingsSchedularInterestPosterTask; @Autowired private SavingsAccountWritePlatformService savingsAccountWritePlatformService; @Autowired diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingTasklet.java index 90407ae23..5d5e82494 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingTasklet.java @@ -37,7 +37,7 @@ import org.apache.fineract.infrastructure.core.domain.FineractContext; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.portfolio.savings.data.SavingsAccountData; import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService; -import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPoster; +import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPosterTask; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -138,13 +138,13 @@ public class PostInterestForSavingTasklet implements Tasklet { for (long i = 0; i < loopCount; i++) { List<SavingsAccountData> subList = safeSubList(savingsAccounts, fromIndex, toIndex); - SavingsSchedularInterestPoster savingsSchedularInterestPoster = applicationContext - .getBean(SavingsSchedularInterestPoster.class); - savingsSchedularInterestPoster.setSavingAccounts(subList); - savingsSchedularInterestPoster.setBackdatedTxnsAllowedTill(backdatedTxnsAllowedTill); - savingsSchedularInterestPoster.setContext(ThreadLocalContextUtil.getContext()); + SavingsSchedularInterestPosterTask savingsSchedularInterestPosterTask = applicationContext + .getBean(SavingsSchedularInterestPosterTask.class); + savingsSchedularInterestPosterTask.setSavingAccounts(subList); + savingsSchedularInterestPosterTask.setBackdatedTxnsAllowedTill(backdatedTxnsAllowedTill); + savingsSchedularInterestPosterTask.setContext(ThreadLocalContextUtil.getContext()); - posters.add(savingsSchedularInterestPoster); + posters.add(savingsSchedularInterestPosterTask); if (lastBatch) { break; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index fb9d2d05d..cd8967f91 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -131,7 +131,6 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -1672,7 +1671,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } @Override - @Transactional(propagation = Propagation.REQUIRES_NEW) public void setSubStatusInactive(Long savingsId) { final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); final Set<Long> existingTransactionIds = new HashSet<>(); @@ -1684,15 +1682,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } @Override - @Transactional(propagation = Propagation.REQUIRES_NEW) public void setSubStatusDormant(Long savingsId) { final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); account.setSubStatusDormant(); - this.savingAccountRepositoryWrapper.save(account); + this.savingAccountRepositoryWrapper.saveAndFlush(account); } @Override - @Transactional(propagation = Propagation.REQUIRES_NEW) public void escheat(Long savingsId) { final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); final Set<Long> existingTransactionIds = new HashSet<>(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java index d89814bbf..557e9247e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java @@ -27,14 +27,11 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.UUID; -import java.util.concurrent.Callable; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.accounting.journalentry.domain.JournalEntryType; -import org.apache.fineract.infrastructure.core.domain.FineractContext; import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.savings.data.SavingsAccountData; @@ -47,32 +44,26 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; -/** - * @author manoj - */ - @Slf4j @RequiredArgsConstructor @Setter @Component @Scope("prototype") -public class SavingsSchedularInterestPoster implements Callable<Void> { +public class SavingsSchedularInterestPoster { private static final String SAVINGS_TRANSACTION_IDENTIFIER = "S"; private final SavingsAccountWritePlatformService savingsAccountWritePlatformService; - private final List<SavingsAccountData> savingsAccountDataList = new ArrayList<>(); private final JdbcTemplate jdbcTemplate; private final SavingsAccountReadPlatformService savingsAccountReadPlatformService; private final PlatformSecurityContext platformSecurityContext; + + private final List<SavingsAccountData> savingsAccountDataList = new ArrayList<>(); private Collection<SavingsAccountData> savingAccounts; - private FineractContext context; private boolean backdatedTxnsAllowedTill; - @Override @Transactional(isolation = Isolation.READ_UNCOMMITTED, rollbackFor = Exception.class) - public Void call() throws org.apache.fineract.infrastructure.jobs.exception.JobExecutionException { - ThreadLocalContextUtil.init(this.context); + public void postInterest() throws JobExecutionException { if (!savingAccounts.isEmpty()) { List<Throwable> errors = new ArrayList<>(); @@ -103,8 +94,6 @@ public class SavingsSchedularInterestPoster implements Callable<Void> { throw new JobExecutionException(errors); } } - - return null; } private void batchUpdateJournalEntries(final List<SavingsAccountData> savingsAccountDataList, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPosterTask.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPosterTask.java new file mode 100644 index 000000000..bf5f2c899 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPosterTask.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.fineract.portfolio.savings.service; + +import java.util.Collection; +import java.util.concurrent.Callable; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.domain.FineractContext; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.portfolio.savings.data.SavingsAccountData; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * @author manoj + */ + +@Slf4j +@RequiredArgsConstructor +@Component +@Scope("prototype") +public class SavingsSchedularInterestPosterTask implements Callable<Void> { + + private final SavingsSchedularInterestPoster interestPoster; + @Setter + private FineractContext context; + + @Override + public Void call() throws org.apache.fineract.infrastructure.jobs.exception.JobExecutionException { + ThreadLocalContextUtil.init(context); + interestPoster.postInterest(); + return null; + } + + public void setSavingAccounts(Collection<SavingsAccountData> savingAccounts) { + this.interestPoster.setSavingAccounts(savingAccounts); + } + + public void setBackdatedTxnsAllowedTill(boolean backdatedTxnsAllowedTill) { + this.interestPoster.setBackdatedTxnsAllowedTill(backdatedTxnsAllowedTill); + } +}
