Hi everyone,
I'm working on a project in Adobe Experience Manager as a Cloud Service, where I have a large number of Content Fragments (CFs) stored in the DAM. I need to copy them into other language nodes, but I'm facing an issue: when I use the language copy feature on the root folder, it also brings over unpublished content and other data that I don't actually need.
My goal is to copy only the specific CFs, for which I already have the paths, into the target language folder—without including any unrelated content.
So my question is:
Is there a way to trigger the copy of these specific CFs via REST API, using their paths, in AEM as a Cloud Service?
Or is there a recommended alternative approach to achieve this in a clean and efficient way?
Thanks in advance for any suggestions!
Views
Replies
Total Likes
hi @davidef34326447, you can implement a custom OSGi service that leverages AEM's Content Fragment Management APIs. You can then expose this service via a Sling Servlet to create a custom sevlet endpoint that you can trigger using the paths of your specific CFs.
1. Develop a Custom OSGi Service:
Create an OSGi service that will contain the core logic for copying a Content Fragment.
Inject necessary services like ResourceResolverFactory to get a ResourceResolver (and thus a Session) and ContentFragmentManager.
Implement a method, for example, copyContentFragment(String sourcePath, String targetPath, String targetLanguageRoot), which takes the source CF path, the desired target path (or just the target language root, and the service constructs the path).
// Example pseudo-code for the OSGi Service method import com.adobe.cq.dam.cfm.ContentFragment;import com.adobe.cq.dam.cfm.ContentFragmentManager;import com.adobe.cq.dam.cfm.FragmentTemplate;import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceResolverFactory; // ... other imports @Component(service = MyContentFragmentCopyService.class) public class MyContentFragmentCopyService { @Reference private ResourceResolverFactory resolverFactory; @Reference private ContentFragmentManager cfm; public boolean copyContentFragment(String sourceCfPath, String targetParentPath, String newFragmentName) { ResourceResolver resourceResolver = null; try { // Use a service user for programmatic access Map<String, Object> param = Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "my-cf-copy-service"); resourceResolver = resolverFactory.getServiceResourceResolver(param); Resource sourceResource = resourceResolver.getResource(sourceCfPath); if (sourceResource == null) { // Log error: source CF not found return false; } ContentFragment sourceFragment = sourceResource.adaptTo(ContentFragment.class); if (sourceFragment == null) { // Log error: resource is not a Content Fragment return false; } // Get the model to create the new fragment FragmentTemplate fragmentTemplate = sourceFragment.getTemplate(); // Or resolve model path directly // Create the new fragment in the target parent path // This method will handle the structure based on the model ContentFragment newFragment = cfm.createContentFragment( resourceResolver.getResource(targetParentPath), newFragmentName, fragmentTemplate, true); // true to auto-create missing folders if (newFragment == null) { // Log error: failed to create new fragment return false; } // Copy content from source elements to new fragment elements for (String elementName : sourceFragment.getElements()) { Object elementValue = sourceFragment.getElement(elementName).getContent(); newFragment.getElement(elementName).setContent(elementValue, sourceFragment.getElement(elementName).getContentType()); } // If you have variations and need to copy them explicitly // Note: The content fragment API for copying variations can be more complex, // you might need to iterate and create variations and set content. // For a simpler approach, you might just copy the master and allow authors to create variations in target. resourceResolver.commit(); // Save changes return true; } catch (Exception e) { // Log and handle exceptions return false; } finally { if (resourceResolver != null && resourceResolver.isLive()) { resourceResolver.close(); } } } }
2. Expose a Sling Servlet:
Create a Sling Servlet that acts as your custom endpoint.
This servlet will receive HTTP POST requests with parameters for the source CF path and the target language path.
Inside the doPost method of the servlet, retrieve the parameters, inject your MyContentFragmentCopyService, and call its copyContentFragment method.
Return an appropriate JSON response indicating success or failure.
// Example pseudo-code for the Sling Servlet 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.osgi.service.component.annotations.Reference; // ... other imports @Component(service = Servlet.class, property = { "sling.servlet.paths=/bin/myproject/copycf", // Your custom endpoint path "sling.servlet.methods=POST" }) public class MyContentFragmentCopyServlet extends SlingAllMethodsServlet { @Reference private MyContentFragmentCopyService cfCopyService; @Override protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException { response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); String sourcePath = request.getParameter("sourcePath"); String targetParentPath = request.getParameter("targetParentPath"); String newFragmentName = request.getParameter("newFragmentName"); // e.g., the name for the new CF if (StringUtils.isBlank(sourcePath) || StringUtils.isBlank(targetParentPath) || StringUtils.isBlank(newFragmentName)) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.getWriter().write("{\"status\":\"error\", \"message\":\"Missing required parameters: sourcePath, targetParentPath, newFragmentName\"}"); return; } try { boolean success = cfCopyService.copyContentFragment(sourcePath, targetParentPath, newFragmentName); if (success) { response.setStatus(HttpServletResponse.SC_OK); response.getWriter().write("{\"status\":\"success\", \"message\":\"Content Fragment copied successfully\"}"); } else { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.getWriter().write("{\"status\":\"error\", \"message\":\"Failed to copy Content Fragment. Check logs.\""); } } catch (Exception e) { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.getWriter().write("{\"status\":\"error\", \"message\":\"An unexpected error occurred: " + e.getMessage() + "\"}"); } } }
3. Define a Service User:
Once deployed, you can trigger the copy for each specific Content Fragment using a simple HTTP POST request to your custom endpoint:
curl -X POST \ "https://your-aem-instance/bin/myproject/copycf" \ -H "Authorization: Bearer <your-access-token>" \ -F "sourcePath=/content/dam/fragments/en/my-fragment" \ -F "targetParentPath=/content/dam/fragments/fr" \ -F "newFragmentName=my-fragment"
Replace <your-aem-instance>, <your-access-token>, and the paths with your actual values.
Views
Replies
Total Likes
thank-you. It's not feasable using ootb assets api?
Views
Replies
Total Likes
For a small number of CFs, you can copy and paste them from the source folder to the desired target folder, similar to how you would with image assets. However, if you have many CFs, manual copy-pasting can become cumbersome.
Views
Replies
Total Likes
Views
Likes
Replies