View Javadoc
1   package org.imageconverter.application;
2   
3   import static java.nio.charset.StandardCharsets.UTF_8;
4   import static java.text.MessageFormat.format;
5   import static org.apache.commons.csv.QuoteMode.NONE;
6   import static org.apache.commons.lang3.StringUtils.EMPTY;
7   import static org.apache.commons.lang3.StringUtils.substringBetween;
8   import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage;
9   
10  import java.io.ByteArrayOutputStream;
11  import java.io.IOException;
12  import java.io.OutputStreamWriter;
13  import java.util.ArrayList;
14  import java.util.List;
15  
16  import javax.validation.Valid;
17  import javax.validation.constraints.NotEmpty;
18  import javax.validation.constraints.NotNull;
19  
20  import org.apache.commons.csv.CSVFormat;
21  import org.apache.commons.csv.CSVPrinter;
22  import org.imageconverter.domain.conversion.ImageConversion;
23  import org.imageconverter.domain.conversion.ImageConversionRepository;
24  import org.imageconverter.infra.exception.CsvFileGenerationException;
25  import org.imageconverter.infra.exception.CsvFileNoDataException;
26  import org.imageconverter.infra.exception.ElementAlreadyExistsException;
27  import org.imageconverter.infra.exception.ElementInvalidException;
28  import org.imageconverter.infra.exception.ElementNotFoundException;
29  import org.imageconverter.infra.exception.ElementWithIdNotFoundException;
30  import org.imageconverter.util.controllers.imageconverter.ImageConversionResponse;
31  import org.imageconverter.util.controllers.imageconverter.ImageConverterRequestInterface;
32  import org.imageconverter.util.logging.Loggable;
33  import org.springframework.dao.InvalidDataAccessApiUsageException;
34  import org.springframework.data.domain.Page;
35  import org.springframework.data.domain.Pageable;
36  import org.springframework.data.jpa.domain.Specification;
37  import org.springframework.stereotype.Service;
38  import org.springframework.transaction.annotation.Transactional;
39  
40  /**
41   * Application service that execute conversion.
42   * 
43   * @author Fernando Romulo da Silva
44   */
45  @Service
46  @Loggable
47  public class ImageConversionService {
48      
49      public static final String [] HEADER_FILE = { "id", "file-name", "result" };
50  
51      private final ImageConversionRepository repository;
52  
53      /**
54       * Default constructor.
55       * 
56       * @param newRepository ImageCoversion repository
57       */
58      ImageConversionService(final ImageConversionRepository newRepository) {
59  	super();
60  	this.repository = newRepository;
61      }
62  
63      /**
64       * Convert an image on text.
65       * 
66       * @param request A image ({@link org.imageconverter.util.controllers.imageconverter.ImageConverterRequest} or {@link org.imageconverter.util.controllers.imageconverter.ImageConverterRequestArea})
67       *                that it'll be convert
68       * @return A {@link ImageConversionResponse} with the conversion
69       * @exception ElementAlreadyExistsException if image (file name) has already converted
70       */
71      @Transactional
72      public ImageConversionResponse convert(@NotNull @Valid final ImageConverterRequestInterface request) {
73  
74  	final var imageConversionNew = new ImageConversion.Builder() //
75  			.with(request) //
76  			.build();
77  
78  	final var fileName = imageConversionNew.getFileName();
79  
80  	repository.findByFileName(fileName).ifPresent(imageConversionResult -> {
81  
82  	    throw new ElementAlreadyExistsException( //
83  			    ImageConversion.class, //
84  			    new Object[] { format("fileName ''{0}''", fileName) } //
85  	    );
86  	});
87  
88  	final var imageConversion = repository.save(imageConversionNew);
89  
90  	return new ImageConversionResponse(imageConversion.getId(), imageConversion.getFileName(), imageConversion.getText());
91      }
92  
93      /**
94       * Convert a list of image on text.
95       * 
96       * @param request A image ({@link org.imageconverter.util.controllers.imageconverter.ImageConverterRequest} or {@link org.imageconverter.util.controllers.imageconverter.ImageConverterRequestArea})
97       *                that it'll be convert
98       * @return A {@link ImageConversionResponse} with the conversion
99       * @exception ElementAlreadyExistsException if image (file name) has already converted
100      */
101     @Transactional
102     public List<ImageConversionResponse> convert(@NotNull @NotEmpty @Valid final List<ImageConverterRequestInterface> requests) {
103 
104 	final var imageList = new ArrayList<ImageConversion>();
105 
106 	for (final var request : requests) {
107 	    final var imageConversionNew = new ImageConversion.Builder() // NOPMD - AvoidInstantiatingObjectsInLoop: It's necessary to create a new object for each element
108 			    .with(request) //
109 			    .build();
110 
111 	    repository.findByFileName(imageConversionNew.getFileName()).ifPresent(imageConversionResult -> {
112 
113 		throw new ElementAlreadyExistsException( //
114 				ImageConversion.class, //
115 				createParams(imageConversionResult) //
116 		);
117 	    });
118 
119 	    imageList.add(imageConversionNew);
120 	}
121 
122 	final var imagesConversion = repository.saveAll(imageList);
123 
124 	return imagesConversion //
125 			.stream() //
126 			.map(img -> new ImageConversionResponse(img.getId(), img.getFileName(), img.getText())) //
127 			.toList();
128 
129     }
130 
131     private Object[] createParams(final ImageConversion imageConversionResult) {
132 	return new Object[] { format("fileName '{0}', id '{1}'", imageConversionResult.getFileName(), imageConversionResult.getId()) };
133     }
134 
135     /**
136      * Delete a conversion image.
137      * 
138      * @param id The image type's id
139      * @exception ElementNotFoundException if conversion (id) doesn't exists
140      */
141     @Transactional
142     public void deleteImageConversion(@NotNull final Long id) {
143 
144 	final var imageConversion = repository.findById(id) //
145 			.orElseThrow(() -> new ElementWithIdNotFoundException(ImageConversion.class, id));
146 
147 	repository.delete(imageConversion);
148 
149 	repository.flush();
150     }
151 
152     /**
153      * Find all stored conversions or a empty list.
154      * 
155      * @return A list of {@link ImageConversionResponse} or a empty list
156      */
157     @Transactional(readOnly = true)
158     public List<ImageConversionResponse> findAll() {
159 
160 	return repository.findAll() //
161 			.stream() //
162 			.map(icr -> new ImageConversionResponse(icr.getId(), icr.getFileName(), icr.getText())) //
163 			.toList(); //
164     }
165 
166     /**
167      * Find a stored conversion by id.
168      * 
169      * @param id The image conversion's id
170      * @return A {@link ImageConversionResponse} object
171      * @exception ElementNotFoundException if a element with id not found
172      */
173     @Transactional(readOnly = true)
174     public ImageConversionResponse findById(@NotNull final Long id) {
175 
176 	final var imageConversion = repository.findById(id) //
177 			.orElseThrow(() -> new ElementWithIdNotFoundException(ImageConversion.class, id));
178 
179 	return new ImageConversionResponse(imageConversion.getId(), imageConversion.getFileName(), imageConversion.getText());
180     }
181 
182     /**
183      * Find image conversions by spring specification
184      * 
185      * @param spec The query specification, a {@link Specification} object
186      * @return A {@link ImageConversionResponse}'s list with result or a empty list
187      * @param pageable The query page control, a {@link Pageable} object
188      * @exception ElementInvalidException if a specification is invalid
189      */
190     @Transactional(readOnly = true)
191     public Page<ImageConversionResponse> findBySpecification(final Specification<ImageConversion> spec, final Pageable page) {
192 
193 	try {
194 
195 	    return repository.findAll(spec, page) //
196 			    .map(icr -> new ImageConversionResponse(icr.getId(), icr.getFileName(), icr.getText())); //
197 
198 	} catch (final InvalidDataAccessApiUsageException ex) {
199 
200 	    final var msgException = getRootCauseMessage(ex);
201 
202 	    final Object[] params = { substringBetween(msgException, "[", "]"), ImageConversion.class.getSimpleName() };
203 
204 	    throw new ElementInvalidException("{exception.ElementInvalidDataSpecification}", ex, params);
205 	}
206     }
207 
208     /**
209      * Create a CSV file based on the result filtered by the spec
210      * 
211      * @param spec A {@link Specification} object, the query filter
212      * @return An array of bytes (file in memory) 
213      */
214     @Transactional(readOnly = true)
215     public byte[] findBySpecificationToCsv(final Specification<ImageConversion> spec) {
216 
217 	final var pageable = Pageable.ofSize(1000);
218 
219 	// TODO replace Page to Slice when it supports Specification
220 	var page = repository.findAll(spec, pageable);
221 
222 	if (page.isEmpty()) {
223 	    throw new CsvFileNoDataException("{exception.csvEmpyResult}", spec);
224 	}
225 
226 	final var csvFormat = CSVFormat.Builder.create() //
227 			.setHeader(HEADER_FILE) //
228 			.setAllowMissingColumnNames(true) //
229 			.setNullString(EMPTY) //
230 			.setQuoteMode(NONE) //
231 			.setDelimiter(';') //
232 			.setEscape(' ') //
233 			.build();
234 
235 	try (final var byteArrayOutputStream = new ByteArrayOutputStream(); //
236 			final var outStreamWriter = new OutputStreamWriter(byteArrayOutputStream, UTF_8); //
237 			final var csvPrinter = new CSVPrinter(outStreamWriter, csvFormat);) {
238 
239 	    do {
240 
241 		var imageConversionList = page.getContent();
242 
243 		for (final var imageConversion : imageConversionList) {
244 		    csvPrinter.printRecord(imageConversion.getId().toString(), imageConversion.getFileName(), imageConversion.getText());
245 		}
246 
247 		csvPrinter.flush();
248 
249 		page = repository.findAll(spec, page.nextOrLastPageable());
250 		imageConversionList = page.getContent();
251 
252 	    } while (page.hasNext());
253 
254 	    return byteArrayOutputStream.toByteArray();
255 
256 	} catch (final IOException ex) {
257 	    throw new CsvFileGenerationException("{exception.csvUnexpectedError}", ex);
258 	}
259     }
260 }