Skip to content

Commit 712c623

Browse files
committed
Import series: add support for extracting Michel catalog numbers in a form "#xxx-xyz".
Fix #694
1 parent 379e013 commit 712c623

26 files changed

+291
-15
lines changed

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

+6
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ public static final class SeriesImportRequestStatus {
5656
public static final String IMPORT_SUCCEEDED = "ImportSucceeded";
5757
}
5858

59+
public static final class SeriesImportParsedData {
60+
// see the following migration:
61+
// 2018-07-05--series_import_parsed_data_michel_numbers_field.xml
62+
public static final int MICHEL_NUMBERS_LENGTH = 19;
63+
}
64+
5965
public static final class SeriesSales {
6066
public static final int TRANSACTION_URL_LENGTH = 255;
6167
}

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

+9
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,15 @@
4444
import ru.mystamps.web.controller.dto.ImportSeriesForm;
4545
import ru.mystamps.web.controller.dto.ImportSeriesSalesForm;
4646
import ru.mystamps.web.controller.dto.RequestImportForm;
47+
import ru.mystamps.web.controller.editor.ExpandCatalogNumbersEditor;
4748
import ru.mystamps.web.controller.event.ImportRequestCreated;
4849
import ru.mystamps.web.dao.dto.ImportRequestDto;
4950
import ru.mystamps.web.dao.dto.SeriesParsedDataDto;
5051
import ru.mystamps.web.dao.dto.SeriesSaleParsedDataDto;
5152
import ru.mystamps.web.service.SeriesImportService;
5253
import ru.mystamps.web.service.SeriesSalesImportService;
5354
import ru.mystamps.web.service.SeriesSalesService;
55+
import ru.mystamps.web.util.CatalogUtils;
5456
import ru.mystamps.web.util.LocaleUtils;
5557

5658
import static ru.mystamps.web.controller.ControllerUtils.redirectTo;
@@ -70,6 +72,12 @@ protected void initRequestImportForm(WebDataBinder binder) {
7072
binder.registerCustomEditor(String.class, "url", new StringTrimmerEditor(true));
7173
}
7274

75+
@InitBinder("importSeriesForm")
76+
protected void initImportSeriesForm(WebDataBinder binder) {
77+
// CheckStyle: ignore LineLength for next 1 line
78+
binder.registerCustomEditor(String.class, "michelNumbers", new ExpandCatalogNumbersEditor());
79+
}
80+
7381
@GetMapping(Url.REQUEST_IMPORT_SERIES_PAGE)
7482
public void showRequestImportForm(Model model) {
7583
RequestImportForm requestImportForm = new RequestImportForm();
@@ -133,6 +141,7 @@ public String showRequestAndImportSeriesForm(
133141
if (series.getPerforated() != null) {
134142
form.setPerforated(series.getPerforated());
135143
}
144+
form.setMichelNumbers(CatalogUtils.toShortForm(series.getMichelNumbers()));
136145
}
137146

138147
SeriesSaleParsedDataDto seriesSale = seriesSalesImportService.getParsedData(requestId);

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

+20
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,24 @@ public void seriesInfoWithNewSeller(HttpServletResponse response) throws IOExcep
112112
);
113113
}
114114

115+
@GetMapping("/test/valid/series-info/catalog-numbers-in-description")
116+
public void seriesInfoWithCatalogNumbersInDescription(HttpServletResponse response)
117+
throws IOException {
118+
119+
printHtml(
120+
response,
121+
"<!DOCTYPE html>"
122+
+ "<html>"
123+
+ "<head>"
124+
+ "<title>Series info (catalog numbers in description)</title>"
125+
+ "</head>"
126+
+ "<body>"
127+
+ "Image: <a id=\"series-image-link-1\" href=\"/image/1\">series image</a>"
128+
+ "<br />"
129+
+ "Info: <span class=\"dl-horizontal\">Спорт, 17 марок, Mi# 2242-2246</span>"
130+
+ "</body>"
131+
+ "</html>"
132+
);
133+
}
134+
115135
}

src/main/java/ru/mystamps/web/controller/dto/ImportSeriesForm.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import ru.mystamps.web.controller.converter.annotation.Country;
3636
import ru.mystamps.web.dao.dto.LinkEntityDto;
3737
import ru.mystamps.web.service.dto.AddSeriesDto;
38+
import ru.mystamps.web.support.beanvalidation.CatalogNumbers;
3839

3940
import static ru.mystamps.web.validation.ValidationRules.MAX_STAMPS_IN_SERIES;
4041
import static ru.mystamps.web.validation.ValidationRules.MIN_RELEASE_YEAR;
@@ -85,6 +86,15 @@ public class ImportSeriesForm implements AddSeriesDto, NullableImageUrl {
8586
@NotNull
8687
private MultipartFile downloadedImage;
8788

89+
// @todo #694 Import series: add support for Scott catalog numbers
90+
// @todo #694 Import series: add support for Yvert catalog numbers
91+
// @todo #694 Import series: add support for Gibbons catalog numbers
92+
// @todo #694 Import series: add support for Zagorski catalog numbers
93+
// @todo #694 Import series: add support for Solovyov catalog numbers
94+
// @todo #694 /series/import/request/{id}(michelNumbers): add integration test for validation
95+
@CatalogNumbers
96+
private String michelNumbers;
97+
8898
@Valid
8999
private ImportSellerForm seller;
90100

@@ -111,11 +121,6 @@ public Integer getMonth() {
111121
return null;
112122
}
113123

114-
@Override
115-
public String getMichelNumbers() {
116-
return null;
117-
}
118-
119124
@Override
120125
public BigDecimal getMichelPrice() {
121126
return null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (C) 2009-2018 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.controller.editor;
19+
20+
import java.beans.PropertyEditorSupport;
21+
import java.util.Set;
22+
23+
import org.springframework.util.StringUtils;
24+
25+
import lombok.RequiredArgsConstructor;
26+
27+
import ru.mystamps.web.util.CatalogUtils;
28+
29+
/**
30+
* Expands range of catalog numbers (1-3) into a comma-separated list (1,2,3).
31+
*
32+
* @author Slava Semushin
33+
* @todo #694 ExpandCatalogNumbersEditor: add unit tests
34+
*/
35+
@RequiredArgsConstructor
36+
public class ExpandCatalogNumbersEditor extends PropertyEditorSupport {
37+
38+
// We can't use StringUtils.EMPTY constant from commons-lang because
39+
// we are using StringUtils from Spring.
40+
private static final String EMPTY = "";
41+
42+
@Override
43+
public void setAsText(String text) throws IllegalArgumentException {
44+
String result = null;
45+
46+
if (text != null) {
47+
// Ideally, an input value should be pre-processed by StringTrimmerEditor(" ", true)
48+
// to remove all spaces and trim string into null if needed. But we can't use
49+
// StringTrimmerEditor because "only one single registered custom editor per property
50+
// path is supported". That's why we have to duplicate logic of the StringTrimmerEditor
51+
// here.
52+
//
53+
// @todo #694 ExpandCatalogNumbersEditor: find a better way of editors composition
54+
String value = text.trim();
55+
value = StringUtils.deleteAny(value, " ");
56+
57+
if (!value.equals(EMPTY)) {
58+
try {
59+
// @todo #694 /series/import/request/{id}:
60+
// add integration test for trimming of michel numbers
61+
// @todo #694 CatalogUtils: consider introducing toLongForm(String) method
62+
Set<String> catalogNumbers = CatalogUtils.parseCatalogNumbers(value);
63+
result = String.join(",", catalogNumbers);
64+
65+
} catch (IllegalArgumentException ignored) { // NOPMD: EmptyCatchBlock
66+
// Intentionally empty: invalid values should be retain as-is.
67+
// They will be rejected later during validation.
68+
}
69+
}
70+
}
71+
72+
setValue(result);
73+
}
74+
75+
}

src/main/java/ru/mystamps/web/controller/event/DownloadingSucceededEventListener.java

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public void onApplicationEvent(DownloadingSucceeded event) {
9393
info.getIssueDate(),
9494
info.getQuantity(),
9595
info.getPerforated(),
96+
info.getMichelNumbers(),
9697
info.getSellerName(),
9798
info.getSellerUrl(),
9899
info.getPrice(),

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,20 @@ public class AddSeriesParsedDataDbDto {
3333
private Integer releaseYear;
3434
private Integer quantity;
3535
private Boolean perforated;
36+
private String michelNumbers;
3637
private Date createdAt;
3738
private Date updatedAt;
3839

40+
// I like parentheses around catalog number check
41+
@SuppressWarnings("PMD.UselessParentheses")
3942
public boolean hasAtLeastOneFieldFilled() {
4043
return categoryId != null
4144
|| countryId != null
4245
|| imageUrl != null
4346
|| releaseYear != null
4447
|| quantity != null
45-
|| perforated != null;
48+
|| perforated != null
49+
|| michelNumbers != null;
4650
}
4751

4852
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ public class SeriesParsedDataDto {
2929
private final Integer issueYear;
3030
private final Integer quantity;
3131
private final Boolean perforated;
32+
private final String michelNumbers;
3233
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ public void addParsedData(Integer requestId, AddSeriesParsedDataDbDto data) {
213213
params.put("updated_at", data.getUpdatedAt());
214214
params.put("quantity", data.getQuantity());
215215
params.put("perforated", data.getPerforated());
216+
params.put("michel_numbers", data.getMichelNumbers());
216217

217218
KeyHolder holder = new GeneratedKeyHolder();
218219

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -346,14 +346,16 @@ public static SeriesParsedDataDto forSeriesParsedDataDto(ResultSet rs, int unuse
346346
Integer releaseYear = JdbcUtils.getInteger(rs, "release_year");
347347
Integer quantity = JdbcUtils.getInteger(rs, "quantity");
348348
Boolean perforated = JdbcUtils.getBoolean(rs, "perforated");
349+
String michelNumbers = rs.getString("michel_numbers");
349350

350351
return new SeriesParsedDataDto(
351352
category,
352353
country,
353354
imageUrl,
354355
releaseYear,
355356
quantity,
356-
perforated
357+
perforated,
358+
michelNumbers
357359
);
358360
}
359361

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

+16
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.net.URISyntaxException;
2222
import java.util.Date;
2323
import java.util.List;
24+
import java.util.Set;
2425

2526
import org.apache.commons.lang3.StringUtils;
2627
import org.apache.commons.lang3.Validate;
@@ -34,6 +35,7 @@
3435

3536
import lombok.RequiredArgsConstructor;
3637

38+
import ru.mystamps.web.Db;
3739
import ru.mystamps.web.Db.SeriesImportRequestStatus;
3840
import ru.mystamps.web.controller.event.ParsingFailed;
3941
import ru.mystamps.web.dao.SeriesImportDao;
@@ -51,6 +53,7 @@
5153
import ru.mystamps.web.service.dto.RequestImportDto;
5254
import ru.mystamps.web.service.dto.SeriesExtractedInfo;
5355
import ru.mystamps.web.support.spring.security.HasAuthority;
56+
import ru.mystamps.web.util.CatalogUtils;
5457

5558
// it complains on "Request id must be non null"
5659
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
@@ -200,6 +203,19 @@ public void saveParsedData(Integer requestId, RawParsedDataDto data) {
200203
seriesParsedData.setQuantity(seriesInfo.getQuantity());
201204
seriesParsedData.setPerforated(seriesInfo.getPerforated());
202205

206+
// @todo #694 SeriesImportServiceImpl.saveParsedData(): add unit tests for michel numbers
207+
Set<String> michelNumbers = seriesInfo.getMichelNumbers();
208+
if (!michelNumbers.isEmpty()) {
209+
String shortenedNumbers = CatalogUtils.toShortForm(michelNumbers);
210+
Validate.validState(
211+
shortenedNumbers.length() <= Db.SeriesImportParsedData.MICHEL_NUMBERS_LENGTH,
212+
"Michel numbers (%s) length exceeds max length of the field (%d)",
213+
shortenedNumbers,
214+
Db.SeriesImportParsedData.MICHEL_NUMBERS_LENGTH
215+
);
216+
seriesParsedData.setMichelNumbers(shortenedNumbers);
217+
}
218+
203219
SeriesSalesParsedDataDbDto seriesSalesParsedData = new SeriesSalesParsedDataDbDto();
204220
seriesSalesParsedData.setCreatedAt(now);
205221
seriesSalesParsedData.setUpdatedAt(now);

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

+38-1
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
import java.math.BigDecimal;
2121
import java.util.Arrays;
2222
import java.util.Collections;
23+
import java.util.LinkedHashSet;
2324
import java.util.List;
25+
import java.util.Set;
2426
import java.util.regex.Matcher;
2527
import java.util.regex.Pattern;
2628
import java.util.stream.Collectors;
29+
import java.util.stream.IntStream;
2730
import java.util.stream.Stream;
2831

2932
import org.apache.commons.lang3.StringUtils;
@@ -40,7 +43,7 @@
4043
import ru.mystamps.web.validation.ValidationRules;
4144

4245
@RequiredArgsConstructor
43-
@SuppressWarnings("PMD.TooManyMethods")
46+
@SuppressWarnings({ "PMD.TooManyMethods", "PMD.GodClass" })
4447
public class SeriesInfoExtractorServiceImpl implements SeriesInfoExtractorService {
4548

4649
// Related to RELEASE_YEAR_REGEXP and used in unit tests.
@@ -56,6 +59,10 @@ public class SeriesInfoExtractorServiceImpl implements SeriesInfoExtractorServic
5659
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE
5760
);
5861

62+
// Regular expression matches range of Michel catalog numbers (from 1 to 9999).
63+
private static final Pattern MICHEL_NUMBERS_REGEXP =
64+
Pattern.compile("#[ ]?([1-9][0-9]{0,3})-([1-9][0-9]{0,3})");
65+
5966
// CheckStyle: ignore LineLength for next 4 lines
6067
private static final Pattern VALID_CATEGORY_NAME_EN = Pattern.compile(ValidationRules.CATEGORY_NAME_EN_REGEXP);
6168
private static final Pattern VALID_CATEGORY_NAME_RU = Pattern.compile(ValidationRules.CATEGORY_NAME_RU_REGEXP);
@@ -79,6 +86,7 @@ public SeriesExtractedInfo extract(RawParsedDataDto data) {
7986
Integer releaseYear = extractReleaseYear(data.getReleaseYear());
8087
Integer quantity = extractQuantity(data.getQuantity());
8188
Boolean perforated = extractPerforated(data.getPerforated());
89+
Set<String> michelNumbers = extractMichelNumbers(data.getMichelNumbers());
8290
Integer sellerId = extractSeller(data.getSellerName(), data.getSellerUrl());
8391
String sellerName = extractSellerName(sellerId, data.getSellerName());
8492
String sellerUrl = extractSellerUrl(sellerId, data.getSellerUrl());
@@ -91,6 +99,7 @@ public SeriesExtractedInfo extract(RawParsedDataDto data) {
9199
releaseYear,
92100
quantity,
93101
perforated,
102+
michelNumbers,
94103
sellerId,
95104
sellerName,
96105
sellerUrl,
@@ -254,6 +263,34 @@ protected Boolean extractPerforated(String fragment) {
254263
return null;
255264
}
256265

266+
// @todo #694 SeriesInfoExtractorServiceImpl.extractMichelNumbers(): add unit tests
267+
// @todo #694 SeriesInfoExtractorServiceImpl: support for a single Michel number
268+
// @todo #694 SeriesInfoExtractorServiceImpl: support for a comma separated Michel numbers
269+
protected Set<String> extractMichelNumbers(String fragment) {
270+
if (StringUtils.isBlank(fragment)) {
271+
return Collections.emptySet();
272+
}
273+
274+
log.debug("Determining michel numbers from a fragment: '{}'", fragment);
275+
276+
Matcher matcher = MICHEL_NUMBERS_REGEXP.matcher(fragment);
277+
if (matcher.find()) {
278+
Integer begin = Integer.valueOf(matcher.group(1));
279+
Integer end = Integer.valueOf(matcher.group(2));
280+
if (begin < end) {
281+
Set<String> numbers = IntStream.rangeClosed(begin, end)
282+
.mapToObj(String::valueOf)
283+
.collect(Collectors.toCollection(LinkedHashSet::new));
284+
log.debug("Extracted michel numbers: {}", numbers);
285+
return numbers;
286+
}
287+
}
288+
289+
log.debug("Could not extract michel numbers from a fragment");
290+
291+
return Collections.emptySet();
292+
}
293+
257294
public Integer extractSeller(String name, String url) {
258295
if (StringUtils.isBlank(name) || StringUtils.isBlank(url)) {
259296
return null;

src/main/java/ru/mystamps/web/service/dto/RawParsedDataDto.java

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class RawParsedDataDto {
2929
private final String releaseYear;
3030
private final String quantity;
3131
private final String perforated;
32+
private final String michelNumbers;
3233
private final String sellerName;
3334
private final String sellerUrl;
3435
private final String price;
@@ -42,6 +43,7 @@ public RawParsedDataDto withImageUrl(String url) {
4243
releaseYear,
4344
quantity,
4445
perforated,
46+
michelNumbers,
4547
sellerName,
4648
sellerUrl,
4749
price,

0 commit comments

Comments
 (0)