Skip to content

Commit d484da4

Browse files
committed
Add support for generating image previews.
1 parent 4e01f5a commit d484da4

29 files changed

+357
-20
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ env:
1111
before_script:
1212
- if [ "$SPRING_PROFILES_ACTIVE" = 'travis' ]; then
1313
mysql -u travis -e 'CREATE DATABASE mystamps CHARACTER SET utf8;';
14-
mkdir -p /tmp/uploads;
14+
mkdir -p /tmp/uploads /tmp/preview;
1515
if [ "$TRAVIS_BRANCH" = 'prod' ]; then
1616
pip install --user ansible==2.1.1.0;
1717
fi;

NEWS.txt

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- (functionality) add possibility for adding series sales
1414
- (functionality) add interface for viewing series sales (contributed by Sergey Chechenev)
1515
- (functionality) name of category/country in Russian now are optional fields
16+
- (functionality) preview now is generating after uploading an image
1617

1718
0.3
1819
- (functionality) implemented possibility to user to add series to his collection

pom.xml

+7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
<version>${javax.validation.version}</version>
2323
</dependency>
2424

25+
<dependency>
26+
<groupId>net.coobird</groupId>
27+
<artifactId>thumbnailator</artifactId>
28+
<version>${thumbnailator.version}</version>
29+
</dependency>
30+
2531
<dependency>
2632
<groupId>org.apache.commons</groupId>
2733
<artifactId>commons-lang3</artifactId>
@@ -524,6 +530,7 @@
524530
<subethasmtp.version>3.1.7</subethasmtp.version>
525531
<surefire.plugin.version>2.19.1</surefire.plugin.version>
526532
<testng.version>6.8.8</testng.version>
533+
<thumbnailator.version>0.4.8</thumbnailator.version>
527534

528535
<!-- Redefine default value from spring-boot-dependencies (https://github.com/spring-projects/spring-boot/blob/v1.4.5.RELEASE/spring-boot-dependencies/pom.xml) -->
529536
<thymeleaf-extras-springsecurity4.version>3.0.2.RELEASE</thymeleaf-extras-springsecurity4.version>

src/main/java/ru/mystamps/web/Url.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ public final class Url {
6262

6363
public static final String INFO_COLLECTION_PAGE = "/collection/{slug}";
6464

65-
public static final String GET_IMAGE_PAGE = "/image/{id}";
65+
public static final String GET_IMAGE_PAGE = "/image/{id}";
66+
public static final String GET_IMAGE_PREVIEW_PAGE = "/image/preview/{id}";
6667

6768
public static final String FORBIDDEN_PAGE = "/error/403";
6869
public static final String NOT_FOUND_PAGE = "/error/404";
@@ -142,6 +143,7 @@ public static Map<String, String> asMap(boolean serveContentFromSingleHost) {
142143
map.put("SELECTIZE_CSS", SELECTIZE_CSS);
143144
map.put("SELECTIZE_JS", SELECTIZE_JS);
144145
map.put("GET_IMAGE_PAGE", GET_IMAGE_PAGE);
146+
map.put("GET_IMAGE_PREVIEW_PAGE", GET_IMAGE_PREVIEW_PAGE);
145147
map.put("FAVICON_ICO", FAVICON_ICO);
146148
map.put("MAIN_CSS", MAIN_CSS);
147149
map.put("CATALOG_UTILS_JS", CATALOG_UTILS_JS);
@@ -150,6 +152,7 @@ public static Map<String, String> asMap(boolean serveContentFromSingleHost) {
150152
} else {
151153
// Use separate domain for our own resources
152154
map.put("GET_IMAGE_PAGE", STATIC_RESOURCES_URL + GET_IMAGE_PAGE);
155+
map.put("GET_IMAGE_PREVIEW_PAGE", STATIC_RESOURCES_URL + GET_IMAGE_PREVIEW_PAGE);
153156
map.put("FAVICON_ICO", STATIC_RESOURCES_URL + FAVICON_ICO);
154157
map.put("MAIN_CSS", STATIC_RESOURCES_URL + MAIN_CSS);
155158
map.put("CATALOG_UTILS_JS", STATIC_RESOURCES_URL + CATALOG_UTILS_JS);

src/main/java/ru/mystamps/web/config/ServicesConfig.java

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public CronService getCronService() {
8080
public ImageService getImageService() {
8181
return new ImageServiceImpl(
8282
strategiesConfig.getImagePersistenceStrategy(),
83+
new ThumbnailatorImagePreviewStrategy(),
8384
daoConfig.getImageDao()
8485
);
8586
}

src/main/java/ru/mystamps/web/config/StrategiesConfig.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ class FsStrategiesConfig implements StrategiesConfig {
5757
@Override
5858
public ImagePersistenceStrategy getImagePersistenceStrategy() {
5959
return new FilesystemImagePersistenceStrategy(
60-
env.getRequiredProperty("app.upload.dir")
60+
env.getRequiredProperty("app.upload.dir"),
61+
env.getRequiredProperty("app.preview.dir")
6162
);
6263
}
6364

src/main/java/ru/mystamps/web/controller/ImageController.java

+22
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,27 @@ public void getImage(@PathVariable("id") Integer imageId, HttpServletResponse re
5959
response.getOutputStream().write(image.getData());
6060
}
6161

62+
@GetMapping(Url.GET_IMAGE_PREVIEW_PAGE)
63+
public void getImagePreview(@PathVariable("id") Integer imageId, HttpServletResponse response)
64+
throws IOException {
65+
66+
if (imageId == null) {
67+
response.sendError(HttpServletResponse.SC_NOT_FOUND);
68+
return;
69+
}
70+
71+
ImageDto image = imageService.getOrCreatePreview(imageId);
72+
if (image == null) {
73+
// return original image when error has occurred
74+
getImage(imageId, response);
75+
return;
76+
}
77+
78+
response.setContentType("image/" + image.getType().toLowerCase());
79+
response.setContentLength(image.getData().length);
80+
81+
response.getOutputStream().write(image.getData());
82+
}
83+
6284
}
6385

src/main/java/ru/mystamps/web/dao/ImageDataDao.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@
2121
import ru.mystamps.web.dao.dto.DbImageDto;
2222

2323
public interface ImageDataDao {
24-
DbImageDto findByImageId(Integer imageId);
24+
DbImageDto findByImageId(Integer imageId, boolean preview);
2525
Integer add(AddImageDataDbDto imageData);
2626
}

src/main/java/ru/mystamps/web/dao/dto/AddImageDataDbDto.java

+1
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@
2525
public class AddImageDataDbDto {
2626
private Integer imageId;
2727
private byte[] content;
28+
private boolean preview;
2829
}

src/main/java/ru/mystamps/web/dao/impl/JdbcImageDataDao.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
*/
1818
package ru.mystamps.web.dao.impl;
1919

20-
import java.util.Collections;
2120
import java.util.HashMap;
2221
import java.util.Map;
2322

@@ -48,11 +47,15 @@ public class JdbcImageDataDao implements ImageDataDao {
4847
private String addImageDataSql;
4948

5049
@Override
51-
public DbImageDto findByImageId(Integer imageId) {
50+
public DbImageDto findByImageId(Integer imageId, boolean preview) {
51+
Map<String, Object> params = new HashMap<>();
52+
params.put("image_id", imageId);
53+
params.put("preview", preview);
54+
5255
try {
5356
return jdbcTemplate.queryForObject(
5457
findByImageIdSql,
55-
Collections.singletonMap("image_id", imageId),
58+
params,
5659
RowMappers::forDbImageDto
5760
);
5861
} catch (EmptyResultDataAccessException ignored) {
@@ -65,6 +68,7 @@ public Integer add(AddImageDataDbDto imageData) {
6568
Map<String, Object> params = new HashMap<>();
6669
params.put("image_id", imageData.getImageId());
6770
params.put("content", imageData.getContent());
71+
params.put("preview", imageData.isPreview());
6872

6973
KeyHolder holder = new GeneratedKeyHolder();
7074

src/main/java/ru/mystamps/web/service/DatabaseImagePersistenceStrategy.java

+25-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public void save(MultipartFile file, ImageInfoDto image) {
5353
AddImageDataDbDto imageData = new AddImageDataDbDto();
5454
imageData.setImageId(image.getId());
5555
imageData.setContent(file.getBytes());
56+
imageData.setPreview(false);
5657

5758
Integer id = imageDataDao.add(imageData);
5859
LOG.info("Image data #{} for image #{} has been saved", id, image.getId());
@@ -63,9 +64,21 @@ public void save(MultipartFile file, ImageInfoDto image) {
6364
}
6465
}
6566

67+
@Override
68+
public void savePreview(byte[] data, ImageInfoDto image) {
69+
AddImageDataDbDto imageData = new AddImageDataDbDto();
70+
imageData.setImageId(image.getId());
71+
imageData.setContent(data);
72+
imageData.setPreview(true);
73+
74+
imageDataDao.add(imageData);
75+
76+
LOG.info("Image #{}: preview has been saved", image.getId());
77+
}
78+
6679
@Override
6780
public ImageDto get(ImageInfoDto image) {
68-
DbImageDto imageDto = imageDataDao.findByImageId(image.getId());
81+
DbImageDto imageDto = imageDataDao.findByImageId(image.getId(), false);
6982
if (imageDto == null) {
7083
LOG.warn("Found image without content: #{}", image.getId());
7184
return null;
@@ -74,4 +87,15 @@ public ImageDto get(ImageInfoDto image) {
7487
return imageDto;
7588
}
7689

90+
@Override
91+
public ImageDto getPreview(ImageInfoDto image) {
92+
DbImageDto imageDto = imageDataDao.findByImageId(image.getId(), true);
93+
if (imageDto == null) {
94+
LOG.info("Image #{}: preview not found", image.getId());
95+
return null;
96+
}
97+
98+
return imageDto;
99+
}
100+
77101
}

src/main/java/ru/mystamps/web/service/FilesystemImagePersistenceStrategy.java

+53-4
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ public class FilesystemImagePersistenceStrategy implements ImagePersistenceStrat
3939
LoggerFactory.getLogger(FilesystemImagePersistenceStrategy.class);
4040

4141
private final File storageDir;
42+
private final File previewDir;
4243

43-
public FilesystemImagePersistenceStrategy(String storageDir) {
44+
public FilesystemImagePersistenceStrategy(String storageDir, String previewDir) {
4445
this.storageDir = new File(storageDir);
46+
this.previewDir = new File(previewDir);
4547
}
4648

4749
@PostConstruct
@@ -53,11 +55,29 @@ public void init() {
5355

5456
} else if (!storageDir.canWrite()) {
5557
LOG.warn(
58+
// TODO(java9): log also user: ProcessHandle.current().info().user()
5659
"Directory '{}' exists but isn't writable for the current user! "
5760
+ "Image uploading won't work.",
5861
storageDir
5962
);
6063
}
64+
65+
LOG.info("Image previews will be saved into {} directory", previewDir);
66+
67+
if (!previewDir.exists()) { // NOPMD: ConfusingTernary (it's ok for me)
68+
LOG.warn(
69+
"Directory '{}' doesn't exist! Image preview generation won't work",
70+
previewDir
71+
);
72+
73+
} else if (!previewDir.canWrite()) {
74+
// TODO(java9): log also user: ProcessHandle.current().info().user()
75+
LOG.warn(
76+
"Directory '{}' exists but isn't writable for the current user! "
77+
+ "Image preview generation won't work",
78+
previewDir
79+
);
80+
}
6181
}
6282

6383
@Override
@@ -73,9 +93,27 @@ public void save(MultipartFile file, ImageInfoDto image) {
7393
}
7494
}
7595

96+
@Override
97+
public void savePreview(byte[] data, ImageInfoDto image) {
98+
try {
99+
Path dest = generateFilePath(previewDir, image);
100+
writeToFile(data, dest);
101+
102+
LOG.info("Image preview data has been written into file {}", dest);
103+
104+
} catch (IOException ex) {
105+
throw new ImagePersistenceException(ex);
106+
}
107+
}
108+
76109
@Override
77110
public ImageDto get(ImageInfoDto image) {
78-
return get(storageDir, image);
111+
return get(storageDir, image, true);
112+
}
113+
114+
@Override
115+
public ImageDto getPreview(ImageInfoDto image) {
116+
return get(previewDir, image, false);
79117
}
80118

81119
// protected to allow spying
@@ -92,6 +130,11 @@ protected void writeToFile(MultipartFile file, Path dest) throws IOException {
92130
Files.copy(file.getInputStream(), dest);
93131
}
94132

133+
// protected to allow spying
134+
protected void writeToFile(byte[] data, Path dest) throws IOException {
135+
Files.write(dest, data);
136+
}
137+
95138
// protected to allow spying
96139
protected boolean exists(Path path) {
97140
return Files.exists(path);
@@ -102,10 +145,16 @@ protected byte[] toByteArray(Path dest) throws IOException {
102145
return Files.readAllBytes(dest);
103146
}
104147

105-
private ImageDto get(File dir, ImageInfoDto image) {
148+
private ImageDto get(File dir, ImageInfoDto image, boolean logWarning) {
106149
Path dest = generateFilePath(dir, image);
107150
if (!exists(dest)) {
108-
LOG.warn("Found image without content: #{} ({} doesn't exist)", image.getId(), dest);
151+
if (logWarning) {
152+
LOG.warn(
153+
"Image #{}: content not found ({} doesn't exist)",
154+
image.getId(),
155+
dest
156+
);
157+
}
109158
return null;
110159
}
111160

src/main/java/ru/mystamps/web/service/ImagePersistenceStrategy.java

+2
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,7 @@
2424

2525
public interface ImagePersistenceStrategy {
2626
void save(MultipartFile file, ImageInfoDto image);
27+
void savePreview(byte[] data, ImageInfoDto image);
2728
ImageDto get(ImageInfoDto image);
29+
ImageDto getPreview(ImageInfoDto image);
2830
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (C) 2009-2017 Slava Semushin <[email protected]>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17+
*/
18+
package ru.mystamps.web.service;
19+
20+
public interface ImagePreviewStrategy {
21+
byte[] createPreview(byte[] image);
22+
}

src/main/java/ru/mystamps/web/service/ImageService.java

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
public interface ImageService {
2727
Integer save(MultipartFile file);
2828
ImageDto get(Integer imageId);
29+
ImageDto getOrCreatePreview(Integer imageId);
2930
void addToSeries(Integer seriesId, Integer imageId);
3031
List<Integer> findBySeriesId(Integer seriesId);
3132
}

0 commit comments

Comments
 (0)