ImageTypeService.java

package org.imageconverter.application;

import static org.apache.commons.lang3.StringUtils.substringBetween;
import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage;

import java.util.List;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import org.imageconverter.domain.imagetype.ImageType;
import org.imageconverter.domain.imagetype.ImageTypeRespository;
import org.imageconverter.infra.exception.ElementAlreadyExistsException;
import org.imageconverter.infra.exception.ElementConflictException;
import org.imageconverter.infra.exception.ElementInvalidException;
import org.imageconverter.infra.exception.ElementNotFoundException;
import org.imageconverter.infra.exception.ElementWithIdNotFoundException;
import org.imageconverter.util.controllers.imagetype.CreateImageTypeRequest;
import org.imageconverter.util.controllers.imagetype.ImageTypeResponse;
import org.imageconverter.util.controllers.imagetype.UpdateImageTypeRequest;
import org.imageconverter.util.logging.Loggable;
import org.springframework.boot.logging.LogLevel;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Application service that manages (CRUD) image types.
 * 
 * @author Fernando Romulo da Silva
 */
@Service
@Loggable(value = LogLevel.INFO)
public class ImageTypeService {

    private final ImageTypeRespository repository;
    
    /**
     * Default constructor.
     * 
     * @param newRepository imageType repository
     */
    ImageTypeService(final ImageTypeRespository newRepository) {
	super();
	this.repository = newRepository;
    }

    /**
     * Create a image type.
     * 
     * @param request A image type ({@link CreateImageTypeRequest}) request to create
     * @return A {@link ImageTypeResponse} with the conversion
     * @exception ElementAlreadyExistsException if image type (file extension) has already exists
     */
    @Transactional
    public ImageTypeResponse createImageType(@NotNull @Valid final CreateImageTypeRequest request) {

	final var imageTypeOptional = repository.findByExtension(request.extension());

	if (imageTypeOptional.isPresent()) {
	    final Object[] params = { "extension '" + request.extension() + "'" };

	    throw new ElementAlreadyExistsException(ImageType.class, params);
	}

	final var imageType = new ImageType(request.extension(), request.name(), request.description());

	final var imageConversion = repository.save(imageType);

	return new ImageTypeResponse(imageType.getId(), imageConversion.getExtension(), imageType.getName());
    }

    /**
     * Update a image type.
     * 
     * @param id      The image type's id
     * @param request A image type ({@link UpdateImageTypeRequest}) requested to update
     * @return A {@link ImageTypeResponse} with the update's result
     * @exception ElementNotFoundException if image type (file extension) doesn't exists
     */
    @Transactional
    public ImageTypeResponse updateImageType(@NotNull final Long id, @NotNull @Valid final UpdateImageTypeRequest request) {

	final var imageType = repository.findById(id) //
			.orElseThrow(() -> new ElementWithIdNotFoundException(ImageType.class, id));

	imageType.update(request.extension(), request.name(), request.description());

	final var imageTypeNew = repository.save(imageType);

	return new ImageTypeResponse(imageTypeNew.getId(), imageTypeNew.getExtension(), imageTypeNew.getName());
    }

    /**
     * Delete a image type.
     * 
     * @param id The image type's id
     * @exception ElementNotFoundException if image type (file extension) doesn't exists
     * @exception ElementConflictException if amage type already been used on image conversion
     */
    @Transactional
    public void deleteImageType(@NotNull final Long id) {

	final var imageType = repository.findById(id) //
			.orElseThrow(() -> new ElementWithIdNotFoundException(ImageType.class, id));

	try {

	    repository.delete(imageType);

	    repository.flush();

	} catch (final DataIntegrityViolationException ex) {

	    throw new ElementConflictException("{exception.imageTypeDeleteDataIntegrityViolation}", ex, id.toString());
	}
    }

    /**
     * Find a image type by id.
     * 
     * @param id The image type's id
     * @return A {@link ImageTypeResponse} object
     * @exception ElementNotFoundException if a element with id not found
     */
    @Transactional(readOnly = true)
    public ImageTypeResponse findById(@NotNull final Long id) {

	final var imageType = repository.findById(id) //
			.orElseThrow(() -> new ElementWithIdNotFoundException(ImageType.class, id));

	return new ImageTypeResponse(imageType.getId(), imageType.getExtension(), imageType.getName());
    }

    /**
     * Find all stored image types or a empty list.
     * 
     * @return A list of {@link ImageTypeResponse} or a empty list
     */
    @Transactional(readOnly = true)
    public List<ImageTypeResponse> findAll() {

	return repository.findAll() //
			.stream() //
			.map(imageType -> new ImageTypeResponse(imageType.getId(), imageType.getExtension(), imageType.getName())) //
			.toList();
    }

    /**
     * Find image types by spring specification.
     * 
     * @param spec     The query specification, a {@link Specification} object
     * @param pageable The query page control, a {@link Pageable} object
     * @return A {@link ImageTypeResponse}'s list with result or a empty list
     * @exception ElementInvalidException if a specification is invalid
     */
    @Transactional(readOnly = true)
    public Page<ImageTypeResponse> findBySpecification(final Specification<ImageType> spec, final Pageable pageable) {

	try {

	    return repository.findAll(spec, pageable) //
			    .map(imageType -> new ImageTypeResponse( //
					    imageType.getId(), //
					    imageType.getExtension(), //
					    imageType.getName() //
			    ));

	} catch (final InvalidDataAccessApiUsageException ex) {

	    final var msgException = getRootCauseMessage(ex);

	    final Object[] params = { substringBetween(msgException, "[", "]"), ImageType.class.getSimpleName() };

	    throw new ElementInvalidException("{exception.ElementInvalidDataSpecification}", ex, params);
	}
    }
}