ImageConversionService.java
package org.imageconverter.application;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.text.MessageFormat.format;
import static org.apache.commons.csv.QuoteMode.NONE;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.substringBetween;
import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.imageconverter.domain.conversion.ImageConversion;
import org.imageconverter.domain.conversion.ImageConversionRepository;
import org.imageconverter.infra.exception.CsvFileGenerationException;
import org.imageconverter.infra.exception.CsvFileNoDataException;
import org.imageconverter.infra.exception.ElementAlreadyExistsException;
import org.imageconverter.infra.exception.ElementInvalidException;
import org.imageconverter.infra.exception.ElementNotFoundException;
import org.imageconverter.infra.exception.ElementWithIdNotFoundException;
import org.imageconverter.util.controllers.imageconverter.ImageConversionResponse;
import org.imageconverter.util.controllers.imageconverter.ImageConverterRequestInterface;
import org.imageconverter.util.logging.Loggable;
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 execute conversion.
*
* @author Fernando Romulo da Silva
*/
@Service
@Loggable
public class ImageConversionService {
public static final String [] HEADER_FILE = { "id", "file-name", "result" };
private final ImageConversionRepository repository;
/**
* Default constructor.
*
* @param newRepository ImageCoversion repository
*/
ImageConversionService(final ImageConversionRepository newRepository) {
super();
this.repository = newRepository;
}
/**
* Convert an image on text.
*
* @param request A image ({@link org.imageconverter.util.controllers.imageconverter.ImageConverterRequest} or {@link org.imageconverter.util.controllers.imageconverter.ImageConverterRequestArea})
* that it'll be convert
* @return A {@link ImageConversionResponse} with the conversion
* @exception ElementAlreadyExistsException if image (file name) has already converted
*/
@Transactional
public ImageConversionResponse convert(@NotNull @Valid final ImageConverterRequestInterface request) {
final var imageConversionNew = new ImageConversion.Builder() //
.with(request) //
.build();
final var fileName = imageConversionNew.getFileName();
repository.findByFileName(fileName).ifPresent(imageConversionResult -> {
throw new ElementAlreadyExistsException( //
ImageConversion.class, //
new Object[] { format("fileName ''{0}''", fileName) } //
);
});
final var imageConversion = repository.save(imageConversionNew);
return new ImageConversionResponse(imageConversion.getId(), imageConversion.getFileName(), imageConversion.getText());
}
/**
* Convert a list of image on text.
*
* @param request A image ({@link org.imageconverter.util.controllers.imageconverter.ImageConverterRequest} or {@link org.imageconverter.util.controllers.imageconverter.ImageConverterRequestArea})
* that it'll be convert
* @return A {@link ImageConversionResponse} with the conversion
* @exception ElementAlreadyExistsException if image (file name) has already converted
*/
@Transactional
public List<ImageConversionResponse> convert(@NotNull @NotEmpty @Valid final List<ImageConverterRequestInterface> requests) {
final var imageList = new ArrayList<ImageConversion>();
for (final var request : requests) {
final var imageConversionNew = new ImageConversion.Builder() // NOPMD - AvoidInstantiatingObjectsInLoop: It's necessary to create a new object for each element
.with(request) //
.build();
repository.findByFileName(imageConversionNew.getFileName()).ifPresent(imageConversionResult -> {
throw new ElementAlreadyExistsException( //
ImageConversion.class, //
createParams(imageConversionResult) //
);
});
imageList.add(imageConversionNew);
}
final var imagesConversion = repository.saveAll(imageList);
return imagesConversion //
.stream() //
.map(img -> new ImageConversionResponse(img.getId(), img.getFileName(), img.getText())) //
.toList();
}
private Object[] createParams(final ImageConversion imageConversionResult) {
return new Object[] { format("fileName '{0}', id '{1}'", imageConversionResult.getFileName(), imageConversionResult.getId()) };
}
/**
* Delete a conversion image.
*
* @param id The image type's id
* @exception ElementNotFoundException if conversion (id) doesn't exists
*/
@Transactional
public void deleteImageConversion(@NotNull final Long id) {
final var imageConversion = repository.findById(id) //
.orElseThrow(() -> new ElementWithIdNotFoundException(ImageConversion.class, id));
repository.delete(imageConversion);
repository.flush();
}
/**
* Find all stored conversions or a empty list.
*
* @return A list of {@link ImageConversionResponse} or a empty list
*/
@Transactional(readOnly = true)
public List<ImageConversionResponse> findAll() {
return repository.findAll() //
.stream() //
.map(icr -> new ImageConversionResponse(icr.getId(), icr.getFileName(), icr.getText())) //
.toList(); //
}
/**
* Find a stored conversion by id.
*
* @param id The image conversion's id
* @return A {@link ImageConversionResponse} object
* @exception ElementNotFoundException if a element with id not found
*/
@Transactional(readOnly = true)
public ImageConversionResponse findById(@NotNull final Long id) {
final var imageConversion = repository.findById(id) //
.orElseThrow(() -> new ElementWithIdNotFoundException(ImageConversion.class, id));
return new ImageConversionResponse(imageConversion.getId(), imageConversion.getFileName(), imageConversion.getText());
}
/**
* Find image conversions by spring specification
*
* @param spec The query specification, a {@link Specification} object
* @return A {@link ImageConversionResponse}'s list with result or a empty list
* @param pageable The query page control, a {@link Pageable} object
* @exception ElementInvalidException if a specification is invalid
*/
@Transactional(readOnly = true)
public Page<ImageConversionResponse> findBySpecification(final Specification<ImageConversion> spec, final Pageable page) {
try {
return repository.findAll(spec, page) //
.map(icr -> new ImageConversionResponse(icr.getId(), icr.getFileName(), icr.getText())); //
} catch (final InvalidDataAccessApiUsageException ex) {
final var msgException = getRootCauseMessage(ex);
final Object[] params = { substringBetween(msgException, "[", "]"), ImageConversion.class.getSimpleName() };
throw new ElementInvalidException("{exception.ElementInvalidDataSpecification}", ex, params);
}
}
/**
* Create a CSV file based on the result filtered by the spec
*
* @param spec A {@link Specification} object, the query filter
* @return An array of bytes (file in memory)
*/
@Transactional(readOnly = true)
public byte[] findBySpecificationToCsv(final Specification<ImageConversion> spec) {
final var pageable = Pageable.ofSize(1000);
// TODO replace Page to Slice when it supports Specification
var page = repository.findAll(spec, pageable);
if (page.isEmpty()) {
throw new CsvFileNoDataException("{exception.csvEmpyResult}", spec);
}
final var csvFormat = CSVFormat.Builder.create() //
.setHeader(HEADER_FILE) //
.setAllowMissingColumnNames(true) //
.setNullString(EMPTY) //
.setQuoteMode(NONE) //
.setDelimiter(';') //
.setEscape(' ') //
.build();
try (final var byteArrayOutputStream = new ByteArrayOutputStream(); //
final var outStreamWriter = new OutputStreamWriter(byteArrayOutputStream, UTF_8); //
final var csvPrinter = new CSVPrinter(outStreamWriter, csvFormat);) {
do {
var imageConversionList = page.getContent();
for (final var imageConversion : imageConversionList) {
csvPrinter.printRecord(imageConversion.getId().toString(), imageConversion.getFileName(), imageConversion.getText());
}
csvPrinter.flush();
page = repository.findAll(spec, page.nextOrLastPageable());
imageConversionList = page.getContent();
} while (page.hasNext());
return byteArrayOutputStream.toByteArray();
} catch (final IOException ex) {
throw new CsvFileGenerationException("{exception.csvUnexpectedError}", ex);
}
}
}