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.*; import de.asideas.crowdsource.domain.model.AttachmentValue; import de.asideas.crowdsource.domain.model.FinancingRoundEntity; import de.asideas.crowdsource.domain.model.PledgeEntity; import de.asideas.crowdsource.domain.model.ProjectEntity; import de.asideas.crowdsource.domain.model.UserEntity; import de.asideas.crowdsource.domain.service.user.UserNotificationService; import de.asideas.crowdsource.domain.shared.LikeStatus; import de.asideas.crowdsource.domain.shared.ProjectStatus; import de.asideas.crowdsource.presentation.Pledge; import de.asideas.crowdsource.presentation.project.Attachment; import de.asideas.crowdsource.presentation.project.Project; import de.asideas.crowdsource.repository.FinancingRoundRepository; import de.asideas.crowdsource.repository.PledgeRepository; import de.asideas.crowdsource.repository.ProjectAttachmentRepository; import de.asideas.crowdsource.repository.ProjectRepository; import de.asideas.crowdsource.repository.UserRepository; import de.asideas.crowdsource.repository.*; import de.asideas.crowdsource.security.Roles; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import java.io.InputStream; import java.util.List; import java.util.Optional; import static de.asideas.crowdsource.domain.shared.LikeStatus.LIKE; import static de.asideas.crowdsource.domain.shared.LikeStatus.UNLIKE; import static java.util.stream.Collectors.toList; @Service public class ProjectService { private static final Logger LOG = LoggerFactory.getLogger(ProjectService.class); private final ProjectAttachmentRepository projectAttachmentRepository; private final ProjectRepository projectRepository; private final PledgeRepository pledgeRepository; private final UserRepository userRepository; private final FinancingRoundRepository financingRoundRepository; private final UserNotificationService userNotificationService; private final FinancingRoundService financingRoundService; private final LikeRepository likeRepository; private final ProjectService thisInstance; @Autowired public ProjectService(ProjectRepository projectRepository, PledgeRepository pledgeRepository, UserRepository userRepository, FinancingRoundRepository financingRoundRepository, UserNotificationService userNotificationService, FinancingRoundService financingRoundService, LikeRepository likeRepository, ProjectAttachmentRepository projectAttachmentRepository) { this.projectRepository = projectRepository; this.pledgeRepository = pledgeRepository; this.userRepository = userRepository; this.financingRoundRepository = financingRoundRepository; this.userNotificationService = userNotificationService; this.financingRoundService = financingRoundService; this.projectAttachmentRepository = projectAttachmentRepository; this.likeRepository = likeRepository; this.thisInstance = this; } public Project getProject(String projectId, UserEntity requestingUser) { return project(loadProjectEntity(projectId), requestingUser); } public List<Project> getProjects(UserEntity requestingUser) { final List<ProjectEntity> projects = projectRepository.findAll(); return projects.stream().map(p -> project(p, requestingUser)).collect(toList()); } public Project addProject(Project project, UserEntity creator) { Assert.notNull(project); Assert.notNull(creator); ProjectEntity projectEntity = new ProjectEntity(creator, project, currentFinancingRound()); projectEntity = projectRepository.save(projectEntity); notifyAdminsOnNewProject(projectEntity); LOG.debug("Project added: {}", projectEntity); return project(projectEntity, creator); } public Project modifyProjectStatus(String projectId, ProjectStatus newStatusToApply, UserEntity requestingUser) { ProjectEntity projectEntity = loadProjectEntity(projectId); if (projectEntity.modifyStatus(newStatusToApply)) { projectEntity = projectRepository.save(projectEntity); userNotificationService.notifyCreatorOnProjectStatusUpdate(projectEntity); } return project(projectEntity, requestingUser); } public Project modifyProjectMasterdata(String projectId, Project modifiedProject, UserEntity requestingUser) { ProjectEntity projectEntity = loadProjectEntity(projectId); if (projectEntity.modifyMasterdata(modifiedProject, requestingUser)) { projectEntity = projectRepository.save(projectEntity); userNotificationService.notifyCreatorAndAdminOnProjectModification(projectEntity, requestingUser); LOG.debug("Project updated: {}", projectEntity); } return project(projectEntity, requestingUser); } public void pledge(String projectId, UserEntity userEntity, Pledge pledge) { ProjectEntity projectEntity = loadProjectEntity(projectId); FinancingRoundEntity financingRound = financingRoundService.mostRecentRoundEntity(); if (financingRound != null && financingRound.terminated() && financingRound.getTerminationPostProcessingDone() && userEntity.getRoles().contains(Roles.ROLE_ADMIN)) { if (!financingRound.idenitityEquals(projectEntity.getFinancingRound())) { throw InvalidRequestException.projectTookNotPartInLastFinancingRond(); } thisInstance.pledgeProjectUsingPostRoundBudget(projectEntity, userEntity, pledge); } else { thisInstance.pledgeProjectInFinancingRound(projectEntity, userEntity, pledge); } } public Attachment addProjectAttachment(String projectId, Attachment attachment, UserEntity savingUser) { ProjectEntity projectEntity = loadProjectEntity(projectId); projectEntity.addAttachmentAllowed(savingUser); AttachmentValue attachmentStored = new AttachmentValue(attachment.getName(), attachment.getType()); attachmentStored = projectAttachmentRepository.storeAttachment(attachmentStored, attachment.getPayload()); projectEntity.addAttachment(attachmentStored); projectRepository.save(projectEntity); return Attachment.asResponseWithoutPayload(attachmentStored, projectEntity); } public Attachment loadProjectAttachment(String projectId, Attachment attachmentRequest) { final ProjectEntity project = loadProjectEntity(projectId); final AttachmentValue attachment2Serve = project.findAttachmentByReference(attachmentRequest); final InputStream payload = projectAttachmentRepository.loadAttachment(attachment2Serve); if (payload == null) { LOG.error("A project's attachment file entry's actual binary data couldn't be found: " + "projectId:{}; fileAttachmentMissing: {}", projectId, attachment2Serve); throw new ResourceNotFoundException(); } return Attachment.asResponse(attachment2Serve, project, payload); } public void deleteProjectAttachment(String projectId, Attachment attachmentRequest, UserEntity deletingUser) { final ProjectEntity project = loadProjectEntity(projectId); final AttachmentValue attachment2Delete = project.findAttachmentByReference(attachmentRequest); project.deleteAttachmentAllowed(deletingUser); projectAttachmentRepository.deleteAttachment(attachment2Delete); project.deleteAttachment(attachment2Delete); projectRepository.save(project); } public void likeProject(String projectId, UserEntity user) { final ProjectEntity project = projectRepository.findOne(projectId); toggleStatus(project, user, LIKE); } public void unlikeProject(String projectId, UserEntity user) { final ProjectEntity project = projectRepository.findOne(projectId); toggleStatus(project, user, UNLIKE); } void pledgeProjectInFinancingRound(ProjectEntity projectEntity, UserEntity userEntity, Pledge pledge) { List<PledgeEntity> pledgesSoFar = pledgeRepository.findByProjectAndFinancingRound( projectEntity, projectEntity.getFinancingRound()); // potential problem: race condition. Two simultaneous requests could lead to "over-pledging" PledgeEntity pledgeEntity = projectEntity.pledge( pledge, userEntity, pledgesSoFar); // potential problem: no transaction -> no rollback -- Possible Solution -> sort of mini event sourcing? if (projectEntity.pledgeGoalAchieved()) { projectRepository.save(projectEntity); } userRepository.save(userEntity); pledgeRepository.save(pledgeEntity); LOG.debug("Project pledged: {}", pledgeEntity); } void pledgeProjectUsingPostRoundBudget(ProjectEntity projectEntity, UserEntity userEntity, Pledge pledge) { FinancingRoundEntity financingRound = projectEntity.getFinancingRound(); List<PledgeEntity> postRoundPledges = pledgeRepository.findByFinancingRoundAndCreatedDateGreaterThan( financingRound, financingRound.getEndDate()); int postRoundPledgableBudget = financingRound.postRoundPledgableBudgetRemaining(postRoundPledges); List<PledgeEntity> pledgesSoFar = pledgeRepository.findByProjectAndFinancingRound( projectEntity, projectEntity.getFinancingRound()); PledgeEntity pledgeResult = projectEntity.pledgeUsingPostRoundBudget( pledge, userEntity, pledgesSoFar, postRoundPledgableBudget); if (projectEntity.pledgeGoalAchieved()) { projectRepository.save(projectEntity); } userRepository.save(userEntity); pledgeRepository.save(pledgeResult); LOG.debug("Project pledged using post round budget: {}", pledgeResult); } protected ProjectEntity loadProjectEntity(String projectId) { ProjectEntity projectEntity = projectRepository.findOne(projectId); if (projectEntity == null) { throw new ResourceNotFoundException(); } return projectEntity; } private Project project(ProjectEntity projectEntity, UserEntity requestingUser) { List<PledgeEntity> pledges = pledgeRepository.findByProjectAndFinancingRound(projectEntity, projectEntity.getFinancingRound()); final Optional<LikeEntity> likeEntity = likeRepository.findOneByProjectAndUser(projectEntity, requestingUser); final long likeCount = likeRepository.countByProjectAndStatus(projectEntity, LIKE); return new Project(projectEntity, pledges, requestingUser, likeCount, likeEntity.map(LikeEntity::getStatus).orElse(UNLIKE)); } private FinancingRoundEntity currentFinancingRound() { return financingRoundRepository.findActive(DateTime.now()); } private void notifyAdminsOnNewProject(final ProjectEntity projectEntity) { userRepository.findAllAdminUsers().stream() .map(UserEntity::getEmail) .forEach(emailAddress -> userNotificationService.notifyAdminOnProjectCreation(projectEntity, emailAddress)); } private void toggleStatus(ProjectEntity project, UserEntity user, LikeStatus status) { final Optional<LikeEntity> likeEntityOptional = likeRepository.findOneByProjectAndUser(project, user); if (likeEntityOptional.isPresent()) { final LikeEntity likeEntity = likeEntityOptional.get(); likeEntity.setStatus(status); likeRepository.save(likeEntity); } else { final LikeEntity likeEntity = new LikeEntity(status, project, user); likeRepository.save(likeEntity); } } }