RentalEventListener.java

package com.github.jenkaby.bikerental.equipment.infrastructure.eventlistener;

import com.github.jenkaby.bikerental.equipment.application.mapper.EquipmentCommandToDomainMapper;
import com.github.jenkaby.bikerental.equipment.application.usecase.UpdateEquipmentUseCase;
import com.github.jenkaby.bikerental.equipment.domain.model.Equipment;
import com.github.jenkaby.bikerental.equipment.domain.repository.EquipmentRepository;
import com.github.jenkaby.bikerental.shared.domain.event.RentalCompleted;
import com.github.jenkaby.bikerental.shared.domain.event.RentalCreated;
import com.github.jenkaby.bikerental.shared.domain.event.RentalStarted;
import com.github.jenkaby.bikerental.shared.domain.event.RentalUpdated;
import lombok.extern.slf4j.Slf4j;
import org.springframework.modulith.events.ApplicationModuleListener;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Component
public class RentalEventListener {

    private final EquipmentRepository equipmentRepository;
    private final UpdateEquipmentUseCase updateEquipmentUseCase;
    private final EquipmentCommandToDomainMapper equipmentCommandToDomainMapper;

    public RentalEventListener(
            EquipmentRepository equipmentRepository,
            UpdateEquipmentUseCase updateEquipmentUseCase,
            EquipmentCommandToDomainMapper equipmentCommandToDomainMapper) {
        this.equipmentRepository = equipmentRepository;
        this.updateEquipmentUseCase = updateEquipmentUseCase;
        this.equipmentCommandToDomainMapper = equipmentCommandToDomainMapper;
    }

    @ApplicationModuleListener
    public void onRentalStarted(RentalStarted event) {
        log.info("Received RentalStarted event for equipments {}", event.equipmentIds());
        equipmentRepository.findByIds(event.equipmentIds())
                .forEach(equipment -> setStatusForEquipment(equipment, EquipmentStatus.RENTED.name()));
    }

    @ApplicationModuleListener
    public void onRentalStarted(RentalCreated event) {
        log.info("Received RentalCreated event {}", event);
        equipmentRepository.findByIds(event.equipmentIds())
                .forEach(equipment -> setStatusForEquipment(equipment, EquipmentStatus.RESERVED.name()));
    }

    @ApplicationModuleListener
    public void onRentalCompleted(RentalCompleted event) {
        log.info("Received RentalCompleted event for equipments {}", event.equipmentIds());
        equipmentRepository.findByIds(event.returnedEquipmentIds())
                .forEach(equipment -> setStatusForEquipment(equipment, EquipmentStatus.AVAILABLE.name()));
    }

    @ApplicationModuleListener
    public void onRentalUpdated(RentalUpdated event) {
        log.info("Received RentalUpdated event {}", event);
        if (CollectionUtils.isEmpty(event.currentState().equipmentIds()) && CollectionUtils.isEmpty(event.previousState().equipmentIds())) {
            return;
        }
        if (RentalStatus.isCancelled(event.currentState().rentalStatus())) {
            var ids = Stream.of(event.currentState().equipmentIds(), event.previousState().equipmentIds())
                    .flatMap(Collection::stream)
                    .collect(Collectors.toSet());
            equipmentRepository.findByIds(ids)
                    .forEach(equipment -> setStatusForEquipment(equipment, EquipmentStatus.AVAILABLE.name()));
        }
        // TODO Verify this
        if (RentalStatus.isDraft(event.currentState().rentalStatus())) {
            if (!CollectionUtils.isEmpty(event.previousState().equipmentIds())) {
                equipmentRepository.findByIds(event.previousState().equipmentIds())
                        .forEach(equipment -> setStatusForEquipment(equipment, EquipmentStatus.AVAILABLE.name()));
            }
            if (!CollectionUtils.isEmpty(event.currentState().equipmentIds())) {
                equipmentRepository.findByIds(event.currentState().equipmentIds())
                        .forEach(equipment -> setStatusForEquipment(equipment, EquipmentStatus.RESERVED.name()));
            }
        }
    }

    private void setStatusForEquipment(Equipment equipment, String targetStatus) {
        try {
            if (targetStatus.equals(equipment.getStatusSlug())) {
                log.debug("Equipment {} already in {} status, skipping", equipment.getId(), targetStatus);
                return;
            }
            var command = equipmentCommandToDomainMapper.toUpdateCommand(equipment, targetStatus);
            updateEquipmentUseCase.execute(command);
            log.info("Successfully changed equipment {} status to {}", equipment.getId(), targetStatus);

        } catch (Exception e) {
            log.error("Failed to update equipment {} status to {}: {}", equipment.getId(), targetStatus, e.getMessage(), e);
        }
    }

    enum RentalStatus {
        DRAFT,
        ACTIVE,
        CANCELLED;

        public static boolean isCancelled(String status) {
            return CANCELLED.name().equals(status);
        }

        public static boolean isDraft(String status) {
            return DRAFT.name().equals(status);
        }
    }

    enum EquipmentStatus {
        AVAILABLE,
        RESERVED,
        RENTED
    }
}