RentalReservationApplicationImpl.java

package io.extact.rms.application;

import static io.extact.rms.application.exception.BusinessFlowException.CauseType.*;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;

import io.extact.rms.application.common.LoginUserUtils;
import io.extact.rms.application.domain.RentalItem;
import io.extact.rms.application.domain.Reservation;
import io.extact.rms.application.domain.UserAccount;
import io.extact.rms.application.exception.BusinessFlowException;
import io.extact.rms.application.exception.BusinessFlowException.CauseType;
import io.extact.rms.application.exception.RmsSystemException;
import io.extact.rms.application.service.RentalItemService;
import io.extact.rms.application.service.ReservationService;
import io.extact.rms.application.service.UserAccountService;
import io.extact.rms.platform.debug.DebugSleepInterceptor.DebugSleep;

@Transactional(TxType.REQUIRED)
@ApplicationScoped
@DebugSleep
public class RentalReservationApplicationImpl implements RentalReservationApplication {

    private RentalItemService rentalItemService;
    private ReservationService reservationService;
    private UserAccountService userService;

    private Map<Class<?>, Function<Integer, ?>> entityGetterMap;

    // ----------------------------------------------------- constructor methods

    @Inject
    public RentalReservationApplicationImpl(RentalItemService rentalItemService,
                ReservationService reservationService,
                UserAccountService userAccountService) {
        this.rentalItemService = rentalItemService;
        this.reservationService = reservationService;
        this.userService = userAccountService;

        this.entityGetterMap = Map.of(
                RentalItem.class, rentalItemService::get,
                Reservation.class, reservationService::get,
                UserAccount.class, userAccountService::get
            );
    }


    // ----------------------------------------------------- public methods

    @SuppressWarnings("unchecked")
    public <T> T get(Class<T> entityClass, int id) {
        return (T) entityGetterMap.get(entityClass).apply(id);
    }

    @Override
    public UserAccount authenticate(String loginId, String password) throws BusinessFlowException {
        var user = userService.findByLoginIdAndPasswod(loginId, password);
        if (user == null) {
            throw new BusinessFlowException("The loginId or password is different", NOT_FOUND);
        }
        return user;
    }

    @Override
    public List<Reservation> findReservationByRentalItemAndStartDate(Integer rentalItemId, LocalDate startDate)
            throws BusinessFlowException {
        var reservations = reservationService.findByRentalItemAndStartDate(rentalItemId, startDate);
        if (reservations.isEmpty()) {
            throw new BusinessFlowException("Reservation does not exist for rentalItemId and startDate", NOT_FOUND);
        }
        return reservations.stream()
                .map(this::toTraversedReservation)
                .toList();
    }

    @Override
    public List<Reservation> findReservationByReserverId(int reserverId) {
        var reservations = reservationService.findByReserverId(reserverId);
        return reservations.stream()
                .map(this::toTraversedReservation)
                .toList();
    }

    @Override
    public List<Reservation> findReservationByRentalItemId(int rentalItemId) {
        var reservations = reservationService.findByRentalItemId(rentalItemId);
        return reservations.stream()
                .map(this::toTraversedReservation)
                .toList();
    }

    @Override
    public List<RentalItem> findCanRentedItemAtTerm(LocalDateTime from, LocalDateTime to) {
        var reservedItemIds = reservationService.findOverlappedReservations(from, to).stream()
                .map(Reservation::getRentalItemId)
                .toList();
        return rentalItemService.findAll().stream()
                .filter(item -> !reservedItemIds.contains(item.getId()))
                .toList();
    }

    @Override
    public boolean canRentedItemAtTerm(int rentalItemId, LocalDateTime from, LocalDateTime to) {
        return reservationService.findOverlappedReservation(rentalItemId, from, to) == null;
    }

    @Override
    public List<Reservation> getAllReservations() {
        var reservations = reservationService.findAll();
        return reservations.stream()
                .map(this::toTraversedReservation)
                .toList();
    }

    @Override
    public List<RentalItem> getAllRentalItems() {
        return rentalItemService.findAll();
    }

    @Override
    public List<UserAccount> getAllUserAccounts() {
        return userService.findAll();
    }

    @Override
    public Reservation addReservation(Reservation addReservation) throws BusinessFlowException {

        var rentalItem = rentalItemService.get(addReservation.getRentalItemId());
        if (rentalItem == null) {
            throw new BusinessFlowException("RentalItem does not exist for rentalItemId.", NOT_FOUND);
        }

        // 予約の登録
        var newReservation = reservationService.add(addReservation);

        // 予約オブジェクトの再構成
        var reserver = getUserAccount(addReservation.getUserAccountId());
        newReservation.setRentalItem(rentalItem);
        newReservation.setUserAccount(reserver);

        return newReservation;
    }

    @Override
    public RentalItem addRentalItem(RentalItem addRentalItem) throws BusinessFlowException, RmsSystemException {
        return rentalItemService.add(addRentalItem);
    }

    @Override
    public UserAccount addUserAccount(UserAccount addUserAccount) throws BusinessFlowException {
        return userService.add(addUserAccount);
    }

    @Override
    public RentalItem updateRentalItem(RentalItem updateRentalItem) {
        return rentalItemService.update(updateRentalItem);
    }

    @Override
    public Reservation updateReservation(Reservation updateReservation) {
        var reservation = reservationService.update(updateReservation);
        return toTraversedReservation(reservation);
    }

    @Override
    public UserAccount updateUserAccount(UserAccount updateUserAccount) {
        return userService.update(updateUserAccount);
    }

    @Override
    public void deleteRentalItem(int rentalItemId) throws BusinessFlowException {
        var isRefered = reservationService.hasRefereToRentalItem(rentalItemId);
        if (isRefered) {
            throw new BusinessFlowException("Cannot be deleted because it is referenced in the reservation.", CauseType.REFERED);
        }
        rentalItemService.delete(rentalItemId);
    }

    @Override
    public void deleteReservation(int reservationId) throws BusinessFlowException {
        reservationService.delete(reservationId);
    }

    @Override
    public void deleteUserAccount(int userAccountId) throws BusinessFlowException {
        var isRefered = reservationService.hasRefereToUserAccount(userAccountId);
        if (isRefered) {
            throw new BusinessFlowException("Cannot be deleted because it is referenced in the reservation.", CauseType.REFERED);
        }
        userService.delete(userAccountId);
    }

    @Override
    public void cancelReservation(int reservationId) throws BusinessFlowException {
        reservationService.cancel(reservationId, LoginUserUtils.get().getUserId());
    }

    @Override
    public UserAccount getOwnUserProfile() throws BusinessFlowException {
        var targetId = LoginUserUtils.get().getUserId();
        var userAccount = userService.get(targetId);
        if (userAccount == null) {
            throw new BusinessFlowException("UserAccount does not exist for LoginId.", NOT_FOUND);
        }
        return userAccount;
    }

    @Override
    public UserAccount updateUserProfile(UserAccount updateUserAccount) {
        if (LoginUserUtils.get().getUserId() != updateUserAccount.getId()) {
            throw new BusinessFlowException("other's profile cannot be updated.", CauseType.FORBIDDEN);
        }
        return userService.update(updateUserAccount);
    }

    // ----------------------------------------------------- private methods

    private Reservation toTraversedReservation(Reservation resavation) {
        var rentalItems = getRentalItem(resavation.getRentalItemId());
        resavation.setRentalItem(rentalItems);
        var reservers = getUserAccount(resavation.getUserAccountId());
        resavation.setUserAccount(reservers);
        return resavation;
    }

    private RentalItem getRentalItem(int rentalItemId) {
        return rentalItemService.get(rentalItemId);
    }

    private UserAccount getUserAccount(int userAccountId) {
        return userService.get(userAccountId);
    }
}