MaterialService.java

package com.university.bookstore.service;

import java.util.List;
import java.util.Optional;

import com.university.bookstore.model.Material;
import com.university.bookstore.repository.MaterialRepository;

/**
 * Domain service that orchestrates business logic for material management.
 * Demonstrates hexagonal architecture by coordinating between domain logic and infrastructure.
 * 
 * <p>This service encapsulates business rules and coordinates between the domain layer
 * and the repository layer, maintaining clean separation of concerns.</p>
 * 
 * @author Navid Mohaghegh
 * @version 3.0
 * @since 2024-09-15
 */
public class MaterialService {
    
    private final MaterialRepository repository;
    
    /**
     * Creates a new material service with the specified repository.
     * 
     * @param repository the material repository to use
     */
    public MaterialService(MaterialRepository repository) {
        this.repository = repository;
    }
    
    /**
     * Adds a material to the system with business logic validation.
     * 
     * @param material the material to add
     * @throws InvalidMaterialException if the material is invalid
     */
    public void addMaterial(Material material) {
        validateMaterial(material);
        repository.save(material);
    }
    
    /**
     * Updates an existing material with business logic validation.
     * 
     * @param material the material to update
     * @throws InvalidMaterialException if the material is invalid
     * @throws MaterialNotFoundException if the material doesn't exist
     */
    public void updateMaterial(Material material) {
        validateMaterial(material);
        
        if (!repository.exists(material.getId())) {
            throw new MaterialNotFoundException("Material not found: " + material.getId());
        }
        
        repository.save(material);
    }
    
    /**
     * Finds a material by its ID.
     * 
     * @param id the material ID
     * @return the material if found
     * @throws MaterialNotFoundException if the material doesn't exist
     */
    public Material findMaterial(String id) {
        return repository.findById(id)
            .orElseThrow(() -> new MaterialNotFoundException("Material not found: " + id));
    }
    
    /**
     * Finds a material by its ID, returning Optional.
     * 
     * @param id the material ID
     * @return Optional containing the material if found
     */
    public Optional<Material> findMaterialOptional(String id) {
        return repository.findById(id);
    }
    
    /**
     * Gets all materials in the system.
     * 
     * @return list of all materials
     */
    public List<Material> getAllMaterials() {
        return repository.findAll();
    }
    
    /**
     * Removes a material from the system.
     * 
     * @param id the material ID to remove
     * @return true if the material was removed, false if not found
     */
    public boolean removeMaterial(String id) {
        return repository.delete(id);
    }
    
    /**
     * Checks if a material exists in the system.
     * 
     * @param id the material ID
     * @return true if the material exists
     */
    public boolean materialExists(String id) {
        return repository.exists(id);
    }
    
    /**
     * Gets the total number of materials in the system.
     * 
     * @return the material count
     */
    public long getMaterialCount() {
        return repository.count();
    }
    
    /**
     * Clears all materials from the system.
     */
    public void clearAllMaterials() {
        repository.deleteAll();
    }
    
    /**
     * Validates a material according to business rules.
     * 
     * @param material the material to validate
     * @throws InvalidMaterialException if validation fails
     */
    private void validateMaterial(Material material) {
        if (material == null) {
            throw new InvalidMaterialException("Material cannot be null");
        }
        
        if (material.getId() == null || material.getId().trim().isEmpty()) {
            throw new InvalidMaterialException("Material ID cannot be null or empty");
        }
        
        if (material.getTitle() == null || material.getTitle().trim().isEmpty()) {
            throw new InvalidMaterialException("Material title cannot be null or empty");
        }
        
        if (material.getCreator() == null || material.getCreator().trim().isEmpty()) {
            throw new InvalidMaterialException("Material creator cannot be null or empty");
        }
        
        if (material.getPrice() < 0) {
            throw new InvalidMaterialException("Material price cannot be negative: " + material.getPrice());
        }
        
        if (material.getYear() < 1000 || material.getYear() > 2100) {
            throw new InvalidMaterialException("Material year must be between 1000 and 2100: " + material.getYear());
        }
    }
    
    /**
     * Exception thrown when a material is invalid according to business rules.
     */
    public static class InvalidMaterialException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        
        public InvalidMaterialException(String message) {
            super(message);
        }
        
        public InvalidMaterialException(String message, Throwable cause) {
            super(message, cause);
        }
    }
    
    /**
     * Exception thrown when a requested material is not found.
     */
    public static class MaterialNotFoundException extends RuntimeException {
        private static final long serialVersionUID = 1L;
        
        public MaterialNotFoundException(String message) {
            super(message);
        }
        
        public MaterialNotFoundException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}