AbstractService.kt

package com.example.templateproject.core.service

import com.example.templateproject.api.dto.BaseDTO
import com.example.templateproject.api.dto.PageDetails
import com.example.templateproject.core.exception.BadRequestErrorMessages
import com.example.templateproject.core.exception.BadRequestException
import com.example.templateproject.core.exception.IdNotFoundException
import com.example.templateproject.core.mapper.AbstractMapper
import com.example.templateproject.core.mapper.PageConverter
import com.example.templateproject.persistence.entity.BaseEntity
import com.example.templateproject.persistence.repository.BaseRepository
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import kotlin.reflect.KClass

abstract class AbstractService<E : BaseEntity, D : BaseDTO>(
    protected open val repository: BaseRepository<E>,
    protected open val mapper: AbstractMapper<E, D>,
    protected open val pageConverter: PageConverter,
    protected open val clazz: KClass<E>,
) {
    companion object {
        val DEFAULT_SORT: Sort = Sort.by(Sort.Order.desc("createdDate"), Sort.Order.desc("id"))
    }

    abstract fun validateEntity(entity: E)

    open fun getDefaultSort() = DEFAULT_SORT

    open fun getEntities(pageable: Pageable): PageDetails<D> {
        val pageableToUse = getPageable(pageable)
        return repository
            .findAll(pageableToUse)
            .map { mapper.toDTO(it) }
            .let { pageConverter.createPageDetails(it) }
    }

    open fun getEntityById(id: Long): D =
        repository
            .findById(id)
            .map { mapper.toDTO(it) }
            .orElseThrow { IdNotFoundException(clazz, id) }

    open fun createEntity(dto: D): D {
        if (dto.id != null) {
            throw BadRequestException(BadRequestErrorMessages.ID_MUST_BE_NULL)
        }

        return saveEntity(mapper.toEntity(dto))
            .let { mapper.toDTO(it) }
    }

    open fun updateEntity(dto: D): D {
        val id = dto.id

        if (id == null) {
            throw BadRequestException(BadRequestErrorMessages.ID_MUST_NOT_BE_NULL)
        }

        return repository
            .findById(dto.id!!)
            .map { mapper.toEntity(dto) }
            .map { saveEntity(it) }
            .map { mapper.toDTO(it) }
            .orElseThrow { IdNotFoundException(clazz, dto.id!!) }
    }

    open fun deleteEntity(id: Long) {
        repository.deleteById(id)
    }

    private fun saveEntity(entity: E): E {
        validateEntity(entity)

        return repository.save(entity)
    }

    protected fun getPageable(pageable: Pageable): Pageable =
        if (pageable.sort.isUnsorted) {
            PageRequest.of(pageable.pageNumber, pageable.pageSize, getDefaultSort())
        } else {
            pageable
        }
}