readme docs

This commit is contained in:
2025-12-02 07:21:23 -05:00
parent 7fb24fdde3
commit d7ae362c23
3 changed files with 403 additions and 57 deletions

View File

@@ -320,6 +320,77 @@ public class MerchantFeedImportServiceImpl implements MerchantFeedImportService
// Feed reading + brand resolution
// ---------------------------------------------------------------------
/**
* Open a Reader for either an HTTP(S) URL or a local file path.
*/
private Reader openFeedReader(String feedUrl) throws java.io.IOException {
if (feedUrl.startsWith("http://") || feedUrl.startsWith("https://")) {
return new InputStreamReader(new URL(feedUrl).openStream(), StandardCharsets.UTF_8);
} else {
return java.nio.file.Files.newBufferedReader(
java.nio.file.Paths.get(feedUrl),
StandardCharsets.UTF_8
);
}
}
/**
* Try a few common delimiters (tab, comma, semicolon) and pick the one
* that yields the expected AvantLink-style header set.
*/
private CSVFormat detectCsvFormat(String feedUrl) throws Exception {
char[] delimiters = new char[]{'\t', ',', ';'};
java.util.List<String> requiredHeaders =
java.util.Arrays.asList("SKU", "Manufacturer Id", "Brand Name", "Product Name");
Exception lastException = null;
for (char delimiter : delimiters) {
try (Reader reader = openFeedReader(feedUrl);
CSVParser parser = CSVFormat.DEFAULT.builder()
.setDelimiter(delimiter)
.setHeader()
.setSkipHeaderRecord(true)
.setIgnoreSurroundingSpaces(true)
.setTrim(true)
.build()
.parse(reader)) {
Map<String, Integer> headerMap = parser.getHeaderMap();
if (headerMap != null && headerMap.keySet().containsAll(requiredHeaders)) {
System.out.println(
"IMPORT >>> detected delimiter '" +
(delimiter == '\t' ? "\\t" : String.valueOf(delimiter)) +
"' for feed: " + feedUrl
);
return CSVFormat.DEFAULT.builder()
.setDelimiter(delimiter)
.setHeader()
.setSkipHeaderRecord(true)
.setIgnoreSurroundingSpaces(true)
.setTrim(true)
.build();
} else if (headerMap != null) {
System.out.println(
"IMPORT !!! delimiter '" +
(delimiter == '\t' ? "\\t" : String.valueOf(delimiter)) +
"' produced headers: " + headerMap.keySet()
);
}
} catch (Exception ex) {
lastException = ex;
System.out.println("IMPORT !!! error probing delimiter '" + delimiter +
"' for " + feedUrl + ": " + ex.getMessage());
}
}
if (lastException != null) {
throw lastException;
}
throw new RuntimeException("Could not auto-detect delimiter for feed: " + feedUrl);
}
private List<MerchantFeedRow> readFeedRowsForMerchant(Merchant merchant) {
String rawFeedUrl = merchant.getFeedUrl();
if (rawFeedUrl == null || rawFeedUrl.isBlank()) {
@@ -331,68 +402,41 @@ public class MerchantFeedImportServiceImpl implements MerchantFeedImportService
List<MerchantFeedRow> rows = new ArrayList<>();
try (Reader baseReader = (feedUrl.startsWith("http://") || feedUrl.startsWith("https://"))
? new InputStreamReader(new URL(feedUrl).openStream(), StandardCharsets.UTF_8)
: java.nio.file.Files.newBufferedReader(java.nio.file.Paths.get(feedUrl), StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(baseReader)) {
try {
// Auto-detect delimiter (TSV/CSV/semicolon) based on header row
CSVFormat format = detectCsvFormat(feedUrl);
// --- Step 1: peek at the first line to detect delimiter ---
reader.mark(10_000);
String firstLine = reader.readLine();
reader.reset();
try (Reader reader = openFeedReader(feedUrl);
CSVParser parser = new CSVParser(reader, format)) {
if (firstLine == null || firstLine.isEmpty()) {
throw new RuntimeException("Empty feed received from " + feedUrl);
}
System.out.println("IMPORT >>> detected headers: " + parser.getHeaderMap().keySet());
// --- Step 2: detect delimiter (TSV vs CSV) ---
char delimiter;
if (firstLine.contains("\t")) {
delimiter = '\t'; // TSV (AvantLink-style)
} else if (firstLine.contains(",")) {
delimiter = ','; // CSV
} else {
// Fallback: default to comma
delimiter = ',';
}
// --- Step 3: build CSVFormat with detected delimiter ---
CSVFormat format = CSVFormat.DEFAULT.builder()
.setDelimiter(delimiter)
.setHeader()
.setSkipHeaderRecord(true)
.setIgnoreSurroundingSpaces(true)
.setTrim(true)
.build();
// --- Step 4: parse the rows into MerchantFeedRow records ---
try (CSVParser parser = new CSVParser(reader, format)) {
for (CSVRecord rec : parser) {
MerchantFeedRow row = new MerchantFeedRow(
rec.get("SKU"),
rec.get("Manufacturer Id"),
rec.get("Brand Name"),
rec.get("Product Name"),
rec.get("Long Description"),
rec.get("Short Description"),
rec.get("Department"),
rec.get("Category"),
rec.get("SubCategory"),
rec.get("Thumb URL"),
rec.get("Image URL"),
rec.get("Buy Link"),
rec.get("Keywords"),
rec.get("Reviews"),
parseBigDecimal(rec.get("Retail Price")),
parseBigDecimal(rec.get("Sale Price")),
rec.get("Brand Page Link"),
rec.get("Brand Logo Image"),
rec.get("Product Page View Tracking"),
rec.get("Variants XML"),
rec.get("Medium Image URL"),
rec.get("Product Content Widget"),
rec.get("Google Categorization"),
rec.get("Item Based Commission")
getCsvValue(rec, "SKU"),
getCsvValue(rec, "Manufacturer Id"),
getCsvValue(rec, "Brand Name"),
getCsvValue(rec, "Product Name"),
getCsvValue(rec, "Long Description"),
getCsvValue(rec, "Short Description"),
getCsvValue(rec, "Department"),
getCsvValue(rec, "Category"),
getCsvValue(rec, "SubCategory"),
getCsvValue(rec, "Thumb URL"),
getCsvValue(rec, "Image URL"),
getCsvValue(rec, "Buy Link"),
getCsvValue(rec, "Keywords"),
getCsvValue(rec, "Reviews"),
parseBigDecimal(getCsvValue(rec, "Retail Price")),
parseBigDecimal(getCsvValue(rec, "Sale Price")),
getCsvValue(rec, "Brand Page Link"),
getCsvValue(rec, "Brand Logo Image"),
getCsvValue(rec, "Product Page View Tracking"),
null,
getCsvValue(rec, "Medium Image URL"),
getCsvValue(rec, "Product Content Widget"),
getCsvValue(rec, "Google Categorization"),
getCsvValue(rec, "Item Based Commission")
);
rows.add(row);
@@ -435,6 +479,28 @@ public class MerchantFeedImportServiceImpl implements MerchantFeedImportService
}
}
/**
* Safely get a column value by header name. If the record is "short"
* (fewer values than headers) or the header is missing, return null
* instead of throwing IllegalArgumentException.
*/
private String getCsvValue(CSVRecord rec, String header) {
if (rec == null || header == null) {
return null;
}
if (!rec.isMapped(header)) {
// Header not present at all
return null;
}
try {
return rec.get(header);
} catch (IllegalArgumentException ex) {
System.out.println("IMPORT !!! short record #" + rec.getRecordNumber()
+ " missing column '" + header + "', treating as null");
return null;
}
}
// ---------------------------------------------------------------------
// Misc helpers
// ---------------------------------------------------------------------