Niveau 1
Niveau 2
Se connecter à la communauté
Connectez-vous pour voir tous les badges
Requirement:
Currently, the Sling Distribution Forward Agents utilize the Apache Sling Distribution Packaging – Vault Package Builder Factory to export packages. However, I want to configure a custom Package Builder that can export or replicate files in their native format instead of using the Vault packaging mechanism.
In other words, the goal is to export the content as-is — for example:
If the file is a JSON, it should be exported as a JSON file.
If it’s an HTML file, export it as HTML.
If it’s an image, export it as an image.
At present, the default Vault packaging exports the data in a binary (vault) format, which is not desirable for this use case.
I would like to understand whether this can be achieved through configuration or if it requires a custom code implementation, so I can proceed accordingly.
Currently using the default vault packaging in my forward agent :
Vues
Réponses
Nombre de J’aime
Do we have something like JSON package Builder? I was going through https://experienceleaguecommunities.adobe.com/t5/adobe-experience-manager/issue-is-sling-content-dis... which says
So is there a JSON package builder available? If so - where is it and how to configure the same?
@VishalKa5 - Can you let me know if there's any such thing?
As of now I just want to focus on JSON only - any insights with the above use-case would be very helpful.
Thanks!
Vues
Réponses
Nombre de J’aime
Hi @ac4320,
Short answer - No, Sling Distribution doesn’t include a JSON package builder. The default “Vault Package Builder Factory” always exports in FileVault format. If you want the content to be distributed as raw JSON, HTML, or binary files (instead of zipped JCR content), you’ll need to implement a custom DistributionPackageBuilder.
Here are the details:
The default builder PID is:
org.apache.sling.distribution.serialization.impl.vlt.VaultDistributionPackageBuilderFactory
You can create your own builder by implementing the interface
org.apache.sling.distribution.packaging.DistributionPackageBuilder.
Inside your implementation, you can:
Read resources under the given paths (DistributionRequest)
Serialize them directly as JSON (using Sling’s ResourceResolver + JsonRenderer)
Write them to an output stream or a temporary file
Then register it as an OSGi component (e.g. name=json) and update your Forward Agent config to reference it:
Example use case:
If your content under /content/data/*.json should be replicated as JSON files, your builder could simply export those nodes via Sling’s JSON exporter instead of creating a Vault package.
References:
Vues
Réponses
Nombre de J’aime
@SantoshSai - Can you please help me with the code?
Vues
Réponses
Nombre de J’aime
Can you please elaborate on the usecase? Why do you want to have this?
Vues
Réponses
Nombre de J’aime
@Jörg_Hoh - We currently do not have an AEM Publisher instance in our setup. Instead, we are using a Sling Distribution Agent configured with an endpoint pointing to a servlet, which is responsible for posting the distributed data to a middleware service. For now, as part of a proof of concept (POC), we are saving the files locally on our machine. Once the middleware setup is complete, the data will be sent there accordingly.
At present, when using the out-of-the-box (OOTB) FileVault Package Exporter, the data is exported in a ZIP format, similar to the packages created via the AEM Package Manager.
However, our requirement is slightly different:
For instance, consider the folder /content/dam/fmdita-outputs, which contains subfolders such as child-map-1, parent-map, etc. Each of these subfolders includes JSON files. When we trigger Sling Distribution using our custom agent, we want it to export only the actual JSON files — maintaining the same folder structure — and save them directly in our local file system.
Currently, the default behavior creates a Vault package containing all node data, rather than exporting the original JSON files. Hence, we are exploring the possibility of implementing a custom Package Builder that can handle this logic and directly save or send the JSON files as-is.
I have attached the relevant code snippets from our setup along with the current output generated when using the FileVault Package Builder for reference.
Node structure:
The /content/dam/fmdita-outputs node represents the DAM folder that contains the generated output files. Each subfolder (e.g., child-map-1, parent-map, etc.) holds one or more JSON files — these are the actual output files that we want to replicate or export in their original format (not as part of a Vault package).
Sling Distribution Agent Configuration (org.apache.sling.distribution.agent.impl.ForwardDistributionAgentFactory-json-exporter.cfg.json) :
{
"name": "exporter",
"enabled": true,
"queue.processing.enabled": true,
"allowed.roots": ["/content"],
"packageImporter.endpoints": [
"http://localhost:4502/bin/receiver"
],
"requestAuthorizationStrategy.target": "(name=default)",
"transportSecretProvider.target": "(name=default)"
}
Relevant servlet (configured in endpoint) :
package com.adobe.aem.common.core.servlets;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@Component(
service = Servlet.class,
property = {
"sling.servlet.methods=POST",
"sling.servlet.paths=/bin/receiver"
}
)
public class VaultBinaryExtractorServlet extends SlingAllMethodsServlet {
private static final Logger log = LoggerFactory.getLogger(VaultBinaryExtractorServlet.class);
@Override
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
log.error("VaultBinaryExtractorServlet invoked!");
String contentType = request.getContentType();
log.error("Incoming Content-Type: " + contentType);
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
File rawFile = new File(System.getProperty("java.io.tmpdir"), "received_vault_data_" + timestamp + ".bin");
File jsonFile = new File(System.getProperty("java.io.tmpdir"), "received_vault_data_" + timestamp + "_summary.json");
log.error("Saving incoming stream to: " + rawFile.getAbsolutePath());
try (InputStream inputStream = request.getInputStream();
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(rawFile))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
log.error("Raw binary saved successfully: " + rawFile.length() + " bytes");
try (FileInputStream fis = new FileInputStream(rawFile)) {
byte[] headerBytes = new byte[16];
fis.read(headerBytes);
String header = new String(headerBytes, StandardCharsets.US_ASCII);
log.error("Header signature: " + header);
if (header.startsWith("DSTRPACKMETA")) {
log.error("Detected Distribution Package stream. Extracting metadata and embedded ZIP...");
extractReadableMetadata(rawFile, jsonFile);
extractInnerZip(rawFile);
log.error("Processing complete for file: " + rawFile.getName());
} else {
log.error("No DSTRPACKMETA header detected. Skipping extraction.");
}
} catch (Exception e) {
log.error("Error analyzing binary header", e);
}
// Respond to caller
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("application/json");
String msg = "{\"message\":\"Binary file received and processed successfully.\"}";
response.getOutputStream().write(msg.getBytes(StandardCharsets.UTF_8));
response.getOutputStream().flush();
}
/**
* Extracts ASCII/UTF-8 readable fragments and writes metadata summary as JSON.
*/
private void extractReadableMetadata(File binFile, File jsonFile) {
log.error("Starting lightweight binary-to-JSON conversion...");
Map<String, Object> result = new LinkedHashMap<>();
List<String> readableStrings = new ArrayList<>();
try (InputStream in = new FileInputStream(binFile)) {
byte[] data = in.readAllBytes();
result.put("fileName", binFile.getName());
result.put("fileSizeBytes", data.length);
result.put("header", new String(data, 0, Math.min(16, data.length), StandardCharsets.US_ASCII));
StringBuilder sb = new StringBuilder();
for (byte b : data) {
char c = (char) (b & 0xFF);
if (Character.isISOControl(c) && c != '\n' && c != '\r') {
if (sb.length() >= 4) {
readableStrings.add(sb.toString());
}
sb.setLength(0);
} else {
sb.append(c);
}
}
if (sb.length() >= 4) readableStrings.add(sb.toString());
List<String> probablePaths = readableStrings.stream()
.filter(s -> s.contains("/content/") || s.contains(".json") || s.contains("jcr_root"))
.collect(Collectors.toList());
result.put("readableStringsFound", readableStrings.size());
result.put("probablePaths", probablePaths);
try (Writer writer = new FileWriter(jsonFile)) {
writer.write(toPrettyJson(result));
}
log.error("Readable metadata extracted successfully: " + jsonFile.getAbsolutePath());
} catch (Exception e) {
log.error("Error extracting readable metadata", e);
}
}
/**
* Extracts the inner Vault ZIP stream from DSTRPACKMETA binary and writes it to disk.
*/
private void extractInnerZip(File binFile) {
log.error("Attempting to extract embedded ZIP from binary...");
try (InputStream in = new FileInputStream(binFile)) {
byte[] data = in.readAllBytes();
int zipStart = -1;
for (int i = 0; i < data.length - 1; i++) {
if (data[i] == 'P' && data[i + 1] == 'K') {
zipStart = i;
break;
}
}
if (zipStart == -1) {
log.error("No ZIP signature (PK) found inside binary file!");
return;
}
byte[] zipBytes = Arrays.copyOfRange(data, zipStart, data.length);
File extractedZip = new File(System.getProperty("java.io.tmpdir"),
binFile.getName().replace(".bin", "_inner.zip"));
try (FileOutputStream out = new FileOutputStream(extractedZip)) {
out.write(zipBytes);
}
log.error("Inner ZIP extracted successfully: " + extractedZip.getAbsolutePath());
log.error("You can now unzip this to see jcr_root/... JSON and XML files.");
} catch (Exception e) {
log.error("Error extracting inner ZIP from DSTRPACKMETA", e);
}
}
/**
* Simple pretty JSON formatter (no external libs).
*/
private String toPrettyJson(Map<String, Object> map) {
StringBuilder sb = new StringBuilder("{\n");
for (Map.Entry<String, Object> entry : map.entrySet()) {
sb.append(" \"").append(entry.getKey()).append("\": ");
Object value = entry.getValue();
if (value instanceof String) {
sb.append("\"").append(value.toString().replace("\"", "\\\"")).append("\"");
} else if (value instanceof Collection) {
sb.append("[\n");
for (Object item : (Collection<?>) value) {
sb.append(" \"").append(item.toString().replace("\"", "\\\"")).append("\",\n");
}
if (sb.charAt(sb.length() - 2) == ',') sb.setLength(sb.length() - 2);
sb.append("\n ]");
} else {
sb.append(value);
}
sb.append(",\n");
}
if (sb.charAt(sb.length() - 2) == ',') sb.setLength(sb.length() - 2);
sb.append("\n}");
return sb.toString();
}
}
Output (with FileVault Package Builder) : https://drive.google.com/file/d/1eILYNIP15cQE44cRw_uIkFSazWn_qpw8/view?usp=sharing
If it’s possible to continue using the FileVault Package Builder and achieve the desired behavior — i.e., only post the JSON files — through certain tweaks or configurations in the existing implementation, we are open to exploring that approach as well. Please let me know if this can be done.
Vues
Réponses
Nombre de J’aime
I wonder if the replication mechanism is the right approach to your requirement; replication was built in the first place to get a copy of content from the local instance to a remote one. Technically it can used that way you want, but then you would a custom ContentBuilder and a custom transport handler, which you can configure in the replication agent (assuming this AEM 6.5 ... not sure if/how that works in CS).
Vues
Réponses
Nombre de J’aime
Since we’re on AEMaaCS, we’re specifically looking for AEMaaCS-compliant solutions.
Vues
Réponses
Nombre de J’aime
Vues
Likes
Réponses