package de.asideas.crowdsource.service; import de.asideas.crowdsource.domain.exception.InvalidRequestException; import de.asideas.crowdsource.domain.exception.ResourceNotFoundException; import de.asideas.crowdsource.domain.model.FinancingRoundEntity; import de.asideas.crowdsource.domain.model.PledgeEntity; import de.asideas.crowdsource.domain.model.UserEntity; import de.asideas.crowdsource.presentation.FinancingRound; import de.asideas.crowdsource.domain.service.financinground.FinancingRoundPostProcessor; import de.asideas.crowdsource.repository.FinancingRoundRepository; import de.asideas.crowdsource.repository.PledgeRepository; import de.asideas.crowdsource.repository.ProjectRepository; import de.asideas.crowdsource.repository.UserRepository; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.scheduling.TaskScheduler; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @Service public class FinancingRoundService implements ApplicationListener<ContextRefreshedEvent> { private static final Logger log = LoggerFactory.getLogger(FinancingRoundService.class); private UserRepository userRepository; private FinancingRoundRepository financingRoundRepository; private ProjectRepository projectRepository; private FinancingRoundPostProcessor financingRoundPostProcessor; private TaskScheduler crowdScheduler; private PledgeRepository pledgeRepository; @Autowired public FinancingRoundService(UserRepository userRepository, FinancingRoundRepository financingRoundRepository, ProjectRepository projectRepository, FinancingRoundPostProcessor financingRoundPostProcessor, TaskScheduler crowdScheduler, PledgeRepository pledgeRepository) { this.userRepository = userRepository; this.financingRoundRepository = financingRoundRepository; this.projectRepository = projectRepository; this.financingRoundPostProcessor = financingRoundPostProcessor; this.crowdScheduler = crowdScheduler; this.pledgeRepository = pledgeRepository; } @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { this.reschedulePostProcessingOfFinancingRounds(); } public List<FinancingRound> allFinancingRounds() { final List<FinancingRoundEntity> all = financingRoundRepository.findAll(); if (all.isEmpty()) { return Collections.emptyList(); } return all.stream() .map(this::financingRound) .collect(Collectors.toList()); } public FinancingRound currentlyActiveRound() { final FinancingRoundEntity financingRoundEntity = financingRoundRepository.findActive(DateTime.now()); if (financingRoundEntity == null) { throw new ResourceNotFoundException(); } return financingRound(financingRoundEntity); } /** * @return The financing round that is currently active or has been active most recently. */ public FinancingRound mostRecentRound() { return financingRound(mostRecentRoundEntity()); } public FinancingRoundEntity mostRecentRoundEntity() { final Page<FinancingRoundEntity> pageMostRecent = financingRoundRepository.financingRounds(new PageRequest(0, 1, Sort.Direction.DESC, "createdDate")); if (pageMostRecent.getNumberOfElements() < 1) { throw new ResourceNotFoundException(); } return pageMostRecent.getContent().get(0); } public FinancingRound startNewFinancingRound(FinancingRound creationCommand) { final List<UserEntity> userEntities = findAllNotDeletedUsers(); // create round final FinancingRoundEntity financingRoundEntity = FinancingRoundEntity .newFinancingRound(creationCommand, userEntities.size()); // flush user budget and set new budget final int budgetPerUser = financingRoundEntity.getBudgetPerUser(); userEntities.forEach(userEntity -> { userEntity.setBudget(budgetPerUser); userRepository.save(userEntity); }); final FinancingRoundEntity res = financingRoundRepository.save(financingRoundEntity); projectRepository.findAll().stream() .filter(res::projectEligibleForRound) .forEach(project -> { project.setFinancingRound(res); projectRepository.save(project); }); schedulePostProcessing(res); return financingRound(res); } private List<UserEntity> findAllNotDeletedUsers() { return userRepository.findAll().stream().filter(user -> !user.isDeleted()).collect(Collectors.toList()); } public FinancingRound stopFinancingRound(String financingRoundId) throws ResourceNotFoundException, InvalidRequestException { FinancingRoundEntity financingRoundEntity = financingRoundRepository.findOne(financingRoundId); if (financingRoundEntity == null) { throw new ResourceNotFoundException(); } if (financingRoundEntity.getEndDate().isBeforeNow()) { throw InvalidRequestException.financingRoundAlreadyStopped(); } financingRoundEntity.stopFinancingRound(); financingRoundRepository.save(financingRoundEntity); financingRoundEntity = financingRoundPostProcessor.postProcess(financingRoundEntity); return financingRound(financingRoundEntity); } /** * Post processes a financing round upon its termination according to domain logic implemented * in {@link FinancingRoundPostProcessor} * * @param financingRound the round to be processed after termination */ void schedulePostProcessing(FinancingRoundEntity financingRound) { Assert.hasText(financingRound.getId()); final String financingRoundId = financingRound.getId(); crowdScheduler.schedule(() -> { FinancingRoundEntity entity2Process = financingRoundRepository.findOne(financingRoundId); if (entity2Process == null) { log.warn("The FinancingRoundEntity having ID {}, scheduled for post processing doesn't exist (anymore).", financingRoundId); return; } financingRoundPostProcessor.postProcess(entity2Process); }, financingRound.getEndDate().toDate()); log.info("|-- Scheduled post processing of financing round targeted at: {} for financing round {}.", financingRound.getEndDate().toDate(), financingRound); } /** * Re-Schedules the post processing of active financing rounds as well as actually executes * post processing of financing rounds already terminated in case the application was not active * at point in time when triggering of post processing should have occurred. */ void reschedulePostProcessingOfFinancingRounds() { log.info("Going to (re-) schedule post processing of still active or not already processed inactive financing rounds."); financingRoundRepository.findAll().stream() .filter(fr -> !fr.getTerminationPostProcessingDone()) .forEach(this::schedulePostProcessing); } FinancingRound financingRound(FinancingRoundEntity financingRoundEntity) { List<PledgeEntity> postRoundPledges = null; if (financingRoundEntity.getTerminationPostProcessingDone()) { postRoundPledges = pledgeRepository.findByFinancingRoundAndCreatedDateGreaterThan(financingRoundEntity, financingRoundEntity.getEndDate()); } return new FinancingRound(financingRoundEntity, postRoundPledges); } }