AnimalService.java

package com.ambrosiaandrade.pets.service;

import com.ambrosiaandrade.pets.entities.AnimalEntity;
import com.ambrosiaandrade.pets.enums.AnimalTypeEnum;
import com.ambrosiaandrade.pets.exceptions.BaseException;
import com.ambrosiaandrade.pets.interfaces.IAnimalMapper;
import com.ambrosiaandrade.pets.models.Animal;
import com.ambrosiaandrade.pets.models.Cat;
import com.ambrosiaandrade.pets.models.Dog;
import com.ambrosiaandrade.pets.repositories.AnimalRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
@Slf4j
public class AnimalService {

    private final AnimalRepository animalRepository;
    private final IAnimalMapper animalMapper;

    /**
     * Dependency injection using constructor is preferred for better testability and immutability.
     * An alternative, but not recommended, would be by attribute with @Autowired annotation,
     * */
    public AnimalService(AnimalRepository animalRepository, IAnimalMapper mapper) {
        this.animalRepository = animalRepository;
        this.animalMapper = mapper;
    }

    public Animal saveAnimal(Animal animal) {
        try {
            handleEmptyFields(animal);
            AnimalEntity entity = animalMapper.toEntity(animal);
            var savedAnimal = animalRepository.save(entity);
            return animalMapper.toModel(savedAnimal);
        } catch (DataAccessException e) {
            StackTraceElement stackTraceElement = e.getStackTrace()[0];
            log.error(stackTraceElement.toString());
            throw new BaseException(e.getMessage(), 500);
        }
    }

    public Animal getAnimal(int id) {
        try {
            Optional<AnimalEntity> optionalAnimal = animalRepository.findById(id);
            return optionalAnimal.map(animalMapper::toModel).orElseThrow(() -> new BaseException("Not found animal with " + id + " id", 404));
        } catch (DataAccessException e) {
            StackTraceElement stackTraceElement = e.getStackTrace()[0];
            log.error(stackTraceElement.toString());
            throw new BaseException(e.getMessage(), 500);
        }
    }

    public List<Animal> getAnimals() {
        try {
            var list = animalRepository.findAll();
            List<Animal> animals = new ArrayList<>();
            list.forEach(animalEntity -> animals.add(animalMapper.toModel(animalEntity)));
            return animals;
        } catch (DataAccessException e) {
            StackTraceElement stackTraceElement = e.getStackTrace()[0];
            log.error(stackTraceElement.toString());
            throw new BaseException(e.getMessage(), 500);
        }
    }

    public List<Animal> getAnimalsByType(String type) {
        try {
            AnimalTypeEnum animalType = AnimalTypeEnum.valueOf(type.toUpperCase());
            List<AnimalEntity> list = animalRepository.findByType(animalType);
            return list.stream().map(animalMapper::toModel).toList();
        } catch (IllegalArgumentException e) {
            log.error("Invalid animal type: " + type);
            throw new BaseException("Invalid animal type: " + type, 400);
        } catch (DataAccessException e) {
            StackTraceElement stackTraceElement = e.getStackTrace()[0];
            log.error(stackTraceElement.toString());
            throw new BaseException(e.getMessage(), 500);
        }
    }

    public void deleteAnimal(int id) {
        try {
            animalRepository.deleteById(id);
        } catch (DataAccessException e) {
            StackTraceElement stackTraceElement = e.getStackTrace()[0];
            log.error(stackTraceElement.toString());
            throw new BaseException(e.getMessage(), 500);
        }
    }

    public Animal updateAnimal(Animal animal, int id) {
        try {
            var animalUpdated = animalRepository.findById(id)
                .map(existing -> {
                    updateEntityFields(existing, animal);
                    return existing;
                })
                .orElseThrow(() -> new BaseException("Animal with ID " + id + " not found.", 404));
            return animalMapper.toModel(animalRepository.save(animalUpdated));
        } catch (DataAccessException e) {
            StackTraceElement stackTraceElement = e.getStackTrace()[0];
            log.error(stackTraceElement.toString());
            throw new BaseException(e.getMessage(), 500);
        }
    }

    private void handleEmptyFields(Animal animal) {
        Animal newAnimal = handleAnimalType(animal.getType(), animal.getBirthday());

        animal.setAge(newAnimal.getAge());
        animal.setAgeInHumanYears(newAnimal.getAgeInHumanYears());
        animal.setDiet(newAnimal.getDiet());
    }

    private void updateEntityFields(AnimalEntity existing, Animal newAnimal) {
        existing.setName(newAnimal.getName());
        existing.setBirthday(newAnimal.getBirthday());
        existing.setType(newAnimal.getType());

        newAnimal.calculateAge();
        existing.setAge(newAnimal.getAge());
        existing.setAgeInHumanYears(newAnimal.getAgeInHumanYears());
        existing.setDiet(newAnimal.getDiet());
    }

    private Animal handleAnimalType(AnimalTypeEnum animalType, LocalDate date) {
        return switch (animalType) {
            case CAT -> new Cat(date);
            case DOG -> new Dog(date);
            default -> new Animal(date);
        };
    }

}