@dipti_chauhan suggested good solution. Below you find find example of code to solve a problem:
1. Servlet to Fetch Products and Save as Content Fragments or Resources
This servlet will make an HTTP request to a REST API, fetch product information in JSON format, and save each product as a Content Fragment or a resource with a payloadJSON property. Note: this approach is just an example to store your product info.
package com.example.core.servlets;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.osgi.services.HttpClientBuilderFactory;
import org.apache.http.util.EntityUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.propertytypes.ServiceDescription;
import org.osgi.service.component.propertytypes.ServiceVendor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.Servlet;
import java.io.IOException;
import java.util.Iterator;
@Component(service = Servlet.class,
property = {
"sling.servlet.paths=/bin/fetch-products",
"sling.servlet.methods=POST"
})
@ServiceDescription("Fetch Products and Save as Resources")
public class FetchProductsServlet extends SlingAllMethodsServlet {
private static final Logger log = LoggerFactory.getLogger(FetchProductsServlet.class);
private static final String PRODUCTS_API_URL = "https://api.example.com/products";
private static final String RESOURCE_PATH = "/content/project/products/";
@Reference
private HttpClientBuilderFactory httpClientBuilderFactory;
@Override
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
ResourceResolver resourceResolver = request.getResourceResolver();
ObjectMapper objectMapper = new ObjectMapper();
try (CloseableHttpClient httpClient = httpClientBuilderFactory.newBuilder().build()) {
// Create HTTP GET request
HttpGet httpGet = new HttpGet(PRODUCTS_API_URL);
HttpResponse httpResponse = httpClient.execute(httpGet);
int statusCode = httpResponse.getStatusLine().getStatusCode();
if (statusCode != 200) {
log.error("Failed to fetch products, HTTP error code: {}", statusCode);
response.setStatus(SlingHttpServletResponse.SC_BAD_REQUEST);
return;
}
// Parse JSON response
HttpEntity entity = httpResponse.getEntity();
String jsonString = EntityUtils.toString(entity);
JsonNode products = objectMapper.readTree(jsonString);
// Iterate over products and save each one
Iterator<JsonNode> elements = products.elements();
while (elements.hasNext()) {
JsonNode product = elements.next();
String productCode = product.get("code").asText();
String productJson = objectMapper.writeValueAsString(product);
// Save product as a resource with payloadJSON property
Resource productResource = resourceResolver.getResource(RESOURCE_PATH + productCode);
if (productResource == null) {
productResource = ResourceUtil.getOrCreateResource(resourceResolver, RESOURCE_PATH + productCode, (String) null, null, true);
}
ModifiableValueMap properties = productResource.adaptTo(ModifiableValueMap.class);
properties.put("payloadJSON", productJson);
resourceResolver.commit();
}
response.setStatus(SlingHttpServletResponse.SC_OK);
response.getWriter().write("Products fetched and saved successfully.");
} catch (Exception e) {
log.error("Error fetching and saving products", e);
response.setStatus(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}
2. Servlet to Handle Product Details Page Requests
This servlet will handle requests to a product details page, extract the product code from the URL, retrieve the corresponding product resource, and set it as a request attribute for further using in Sling models. Example of URL: /content/project/product-details.product.P123.html
package com.example.core.servlets;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.propertytypes.ServiceDescription;
import org.osgi.service.component.propertytypes.ServiceVendor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
@Component(service = Servlet.class,
property = {
"sling.servlet.resourceTypes=/content/project/product-details",
"sling.servlet.extensions=html",
"sling.servlet.selectors=product"
})
@ServiceDescription("Product Details Servlet")
public class ProductDetailsServlet extends SlingSafeMethodsServlet {
private static final Logger log = LoggerFactory.getLogger(ProductDetailsServlet.class);
private static final String RESOURCE_PATH = "/content/project/products/";
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
String[] selectors = request.getRequestPathInfo().getSelectors();
if (selectors == null || selectors.length == 0) {
response.sendError(SlingHttpServletResponse.SC_NOT_FOUND);
return;
}
String productCode = selectors[1];
String productPath = RESOURCE_PATH + productCode;
Resource productResource = request.getResourceResolver().getResource(productPath);
if (productResource == null) {
response.sendError(SlingHttpServletResponse.SC_NOT_FOUND, "Product not found");
return;
}
request.setAttribute("productResourcePath", productPath);
// or you can adapt resourc to any Sling Model to to any DTO and pass as a request param
request.getRequestDispatcher("/content/project/product-details.html").forward(request, response);
}
}
3. Example of Sling Model that shows how to reuse request attribute.
@Model(adaptables = Resource.class)
public class ProductModel {
private static final Logger log = LoggerFactory.getLogger(ProductModel.class);
@Self
private ResourceResolver resourceResolver;
@RequestAttribute
@Optional
private String productResourcePath;
private String title;
@PostConstruct
protected void init() {
if (productResourcePath != null) {
Resource productResource = resourceResolver.getResource(productResourcePath);
if (productResource != null) {
String payloadJSON = productResource.getValueMap().get("payloadJSON", String.class);
if (payloadJSON != null) {
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(payloadJSON);
JsonNode titleNode = jsonNode.get("title");
if (titleNode != null) {
title = titleNode.asText();
} else {
log.warn("Title property not found in JSON for resource: {}", productResourcePath);
}
} catch (Exception e) {
log.error("Error parsing JSON for resource: {}", productResourcePath, e);
}
} else {
log.warn("No payloadJSON property found for resource: {}", productResourcePath);
}
} else {
log.warn("No resource found at path: {}", productResourcePath);
}
} else {
log.warn("No productResourcePath found in request attributes.");
}
}
public String getTitle() {
return title;
}
public String getResourcePath() {
return productResourcePath;
}
}
SEO impact
In this case your sitemap will contain only /content/project/product-details.html page. You need to extend sitemap generation to include all product specific URLs (like /content/project/product-details.product.P123.html, /content/project/product-details.product.P124.html) to it. Read more: https://github.com/apache/sling-org-apache-sling-sitemap#readme . However I would suggest to implement vanity urls for product details pages.