fix: send XML body on PS WebService write operations

PS WebService requires XML request body even when output_format=JSON.
Build XML manually in PrestashopClient for createProduct, updateProduct
and updateStockAvailable. Add link_rewrite slug generation and XML
character escaping. Response parsing (JSON) unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
luklpz 2026-05-14 19:14:16 +02:00
parent 588c1db5c3
commit cfce84eb4d
1 changed files with 84 additions and 3 deletions

View File

@ -4,6 +4,7 @@ import com.teterialosjuanjos.tfg.sync_service.config.IntegrationProperties;
import com.teterialosjuanjos.tfg.sync_service.integration.prestashop.dto.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
@ -84,6 +85,7 @@ public class PrestashopClient {
/**
* Creates a new product in PrestaShop.
* PS WebService requires XML request body even when {@code output_format=JSON} is set.
*
* @return the created product including its assigned {@code id}
*/
@ -94,7 +96,8 @@ public class PrestashopClient {
.queryParam("ws_key", wsKey)
.queryParam("output_format", "JSON")
.build())
.body(new PrestashopProductDto.SingleResponse(dto))
.contentType(MediaType.APPLICATION_XML)
.body(toProductXml(dto))
.retrieve()
.body(PrestashopProductDto.SingleResponse.class);
@ -109,7 +112,8 @@ public class PrestashopClient {
.queryParam("ws_key", wsKey)
.queryParam("output_format", "JSON")
.build(id))
.body(new PrestashopProductDto.SingleResponse(dto))
.contentType(MediaType.APPLICATION_XML)
.body(toProductXml(dto))
.retrieve()
.toBodilessEntity();
}
@ -147,7 +151,8 @@ public class PrestashopClient {
.queryParam("ws_key", wsKey)
.queryParam("output_format", "JSON")
.build(id))
.body(new PrestashopStockAvailableDto.SingleResponse(dto))
.contentType(MediaType.APPLICATION_XML)
.body(toStockAvailableXml(dto))
.retrieve()
.toBodilessEntity();
}
@ -194,4 +199,80 @@ public class PrestashopClient {
.body(PrestashopCustomerDto.SingleResponse.class);
return response != null ? response.customer() : null;
}
// ── XML builders ──────────────────────────────────────────────────────
// PS WebService requires XML body for writes; output_format=JSON only affects the response.
private String toProductXml(PrestashopProductDto dto) {
String name = firstLangValue(dto.name());
String description = firstLangValue(dto.description());
String descShort = firstLangValue(dto.descriptionShort());
String slug = toSlug(dto.reference());
StringBuilder sb = new StringBuilder(512);
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.append("<prestashop xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
sb.append("<product>");
if (dto.id() != null) {
sb.append("<id>").append(dto.id()).append("</id>");
}
sb.append("<reference>").append(escapeXml(dto.reference())).append("</reference>");
if (dto.price() != null) {
sb.append("<price>").append(escapeXml(dto.price())).append("</price>");
}
if (dto.active() != null) {
sb.append("<active>").append(escapeXml(dto.active())).append("</active>");
}
if (dto.idCategoryDefault() != null) {
sb.append("<id_category_default>").append(escapeXml(dto.idCategoryDefault())).append("</id_category_default>");
}
sb.append("<id_tax_rules_group>1</id_tax_rules_group>");
sb.append("<link_rewrite><language id=\"1\">").append(escapeXml(slug)).append("</language></link_rewrite>");
sb.append("<name><language id=\"1\">").append(escapeXml(name)).append("</language></name>");
sb.append("<description><language id=\"1\">").append(escapeXml(description)).append("</language></description>");
sb.append("<description_short><language id=\"1\">").append(escapeXml(descShort)).append("</language></description_short>");
sb.append("</product>");
sb.append("</prestashop>");
return sb.toString();
}
private String toStockAvailableXml(PrestashopStockAvailableDto dto) {
StringBuilder sb = new StringBuilder(256);
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.append("<prestashop xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
sb.append("<stock_available>");
if (dto.id() != null) {
sb.append("<id>").append(dto.id()).append("</id>");
}
if (dto.idProduct() != null) {
sb.append("<id_product>").append(dto.idProduct()).append("</id_product>");
}
String idAttr = dto.idProductAttribute() != null ? dto.idProductAttribute() : "0";
sb.append("<id_product_attribute>").append(idAttr).append("</id_product_attribute>");
sb.append("<quantity>").append(dto.quantity()).append("</quantity>");
sb.append("</stock_available>");
sb.append("</prestashop>");
return sb.toString();
}
private static String firstLangValue(List<PrestashopProductDto.LangValue> langs) {
if (langs == null || langs.isEmpty()) return "";
String v = langs.get(0).value();
return v != null ? v : "";
}
/** Generates a URL-safe slug from any string (used for PS link_rewrite). */
private static String toSlug(String text) {
if (text == null || text.isBlank()) return "product";
return text.toLowerCase().replaceAll("[^a-z0-9]+", "-").replaceAll("^-|-$", "");
}
private static String escapeXml(String text) {
if (text == null) return "";
return text.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&apos;");
}
}