27
27
import ru .mystamps .web .feature .image .ImageDb .Images ;
28
28
import ru .mystamps .web .support .spring .security .HasAuthority ;
29
29
30
+ import java .io .IOException ;
30
31
import java .util .List ;
31
32
import java .util .Locale ;
32
33
33
34
import static org .apache .commons .lang3 .StringUtils .substringAfter ;
34
35
import static org .apache .commons .lang3 .StringUtils .substringBefore ;
35
36
37
+ // Complains on duplicated String literal "Image id must be non null"
38
+ @ SuppressWarnings ("PMD.AvoidDuplicateLiterals" )
36
39
@ RequiredArgsConstructor
37
40
public class ImageServiceImpl implements ImageService {
38
41
@@ -76,6 +79,59 @@ public ImageInfoDto save(MultipartFile file) {
76
79
77
80
return imageInfo ;
78
81
}
82
+
83
+ // @todo #1303 ImageServiceImpl.replace(): add unit tests
84
+ // @todo #1303 ImageServiceImpl: reduce duplication between add() and replace()
85
+ // @todo #1303 ImageServiceImpl.replace(): ensure that method cleanups file after exception
86
+ @ Override
87
+ @ Transactional
88
+ @ PreAuthorize (HasAuthority .REPLACE_IMAGE )
89
+ public void replace (Integer imageId , MultipartFile file ) {
90
+ Validate .isTrue (imageId != null , "Image id must be non null" );
91
+ Validate .isTrue (imageId > 0 , "Image id must be greater than zero" );
92
+
93
+ Validate .isTrue (file != null , "File must be non null" );
94
+ Validate .isTrue (file .getSize () > 0 , "Image size must be greater than zero" );
95
+
96
+ String contentType = file .getContentType ();
97
+ Validate .isTrue (contentType != null , "File type must be non null" );
98
+
99
+ String extension = extractExtensionFromContentType (contentType );
100
+ Validate .validState (
101
+ StringUtils .equalsAny (extension , "png" , "jpeg" ),
102
+ "File type must be PNG or JPEG image, but '%s' (%s) were passed" ,
103
+ contentType , extension
104
+ );
105
+
106
+ String imageType = extension .toUpperCase (Locale .ENGLISH );
107
+
108
+ // Trim and abbreviate a filename. It shouldn't fail a process because the field is optional
109
+ String filename = StringUtils .trimToNull (file .getOriginalFilename ());
110
+ filename = abbreviateIfLengthGreaterThan (filename , Images .FILENAME_LENGTH );
111
+
112
+ ImageInfoDto oldImageInfo = imageDao .findById (imageId );
113
+
114
+ imageDao .replace (imageId , imageType , filename );
115
+ log .info (
116
+ "Image #{}: meta data has been replaced by '{}', type={}" ,
117
+ imageId ,
118
+ filename ,
119
+ imageType
120
+ );
121
+
122
+ byte [] image = getBytes (file );
123
+ ImageInfoDto newImageInfo = new ImageInfoDto (imageId , imageType );
124
+ imagePersistenceStrategy .replace (image , oldImageInfo , newImageInfo );
125
+
126
+ // It was also possible to replace an image, remove a preview and let it to be generated
127
+ // later, on demand. But this process will be split in time and if a preview generation
128
+ // fails, we'll end up with inconsistency between an image and its preview. As such issues
129
+ // visible to users and might go unnoticed by admins, we decided to generate both images
130
+ // at the same time and have more guarantees regarding consistency.
131
+ byte [] preview = imagePreviewStrategy .createPreview (image );
132
+ ImageInfoDto previewInfo = ImageInfoDto .newPreview (imageId );
133
+ imagePersistenceStrategy .replacePreview (preview , previewInfo );
134
+ }
79
135
80
136
@ Override
81
137
@ Transactional (readOnly = true )
@@ -97,7 +153,7 @@ public ImageDto getOrCreatePreview(Integer imageId) {
97
153
Validate .isTrue (imageId != null , "Image id must be non null" );
98
154
Validate .isTrue (imageId > 0 , "Image id must be greater than zero" );
99
155
100
- ImageInfoDto previewInfo = new ImageInfoDto (imageId , "jpeg" );
156
+ ImageInfoDto previewInfo = ImageInfoDto . newPreview (imageId );
101
157
102
158
// NB: the race between getPreview() and createReview() is possible.
103
159
// If this happens, the last request will overwrite the first.
@@ -176,4 +232,12 @@ private String abbreviateIfLengthGreaterThan(String text, int maxLength) {
176
232
return StringUtils .abbreviate (text , maxLength );
177
233
}
178
234
235
+ private static byte [] getBytes (MultipartFile file ) {
236
+ try {
237
+ return file .getBytes ();
238
+ } catch (IOException e ) {
239
+ throw new IllegalStateException (e );
240
+ }
241
+ }
242
+
179
243
}
0 commit comments