Expand my Community achievements bar.

Introducing Adobe LLM Optimizer: Own your brand’s presence in AI-Powered search and discovery

Selective Content Fragment Copy via REST API in AEM as a Cloud Service

Avatar

Level 3

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!

3 Replies

Avatar

Level 9

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.

Here the implementation steps:

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).

     
    AI-generate code:
    // 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.


    AI-generate code:
    // 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:

  • For your OSGi service to have the necessary permissions to read source CFs and write new ones in the DAM, you must map a service user to your bundle.
  • Create a system user (e.g., my-cf-copy-service) via the AEM User Administration and grant it appropriate permissions on /content/dam (read for source, modify for target).
  • In your code, refer to this service user in ResourceResolverFactory.SUBSERVICE.
  • Configure the service user mapping in your org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.xml file (or ServiceUserMapper.config if using traditional OSGi config).

 

Once deployed, you can trigger the copy for each specific Content Fragment using a simple HTTP POST request to your custom endpoint:

Bash
 
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.

 

Avatar

Level 3

thank-you. It's not feasable using ootb assets api?

Avatar

Level 9

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.