Real-World Scenarios in AEM
1. Problem Statement
We received a request from the business indicating that a lot of empty folders or nodes have been created inside the DAM and Experience Fragment folders in the production environment. This makes it difficult for authors to find the content they need, as they have to scroll past all these empty folders.
2. Approach to Deleting Empty Folders or Nodes
3. Why a Servlet-Based Approach?
We chose a servlet-based approach because it’s dynamic. This means we can specify paths for deletion. For example, we can delete empty folders or nodes from the DAM by providing the DAM URL, and similarly, we can delete from the Experience Fragment folders by providing the respective URL.
Steps to Follow
4. Backup Production Environment:
Since we’re working in the production environment, the first step is to take a complete backup. This ensures we can restore everything if something goes wrong.
5. Freeze Content Authoring:
We need to temporarily stop all content authoring activities. This prevents any changes while we’re cleaning up the empty folders.
Take Screenshots
Before we start deleting, take screenshots of the DAM and Experience Fragment folders. This helps us document the state before and after the cleanup.
6. Delete Empty Folders:
We’ll use a servlet to delete the empty folders or nodes. The servlet will be dynamic, meaning we can change the URL to target different paths (e.g., DAM or Experience Fragment).
7. Proof of Concept (POC)
Before starting the implementation, I want to explain the POC that I have done.
In the POC, I found that there are some empty properties available in the JCR (Java Content Repository). Based on this, we wrote the logic to check these nodes. If a node is found to be empty, the logic will delete the node.
8. Implementation Steps
let’s start with writing the servlets code that can delete empty folders or nodes dynamically based on specified paths.
Here is the working Code
import java.io.IOException;
import lombok.NonNull;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.ServletResolverConstants;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServletResponse;
import org.apache.sling.api.resource.PersistenceException;
@Component(service = Servlet.class, property = {
Constants.SERVICE_DESCRIPTION + "=Delete Nodes Servlet",
ServletResolverConstants.SLING_SERVLET_PATHS + "=/bin/deletenodes",
ServletResolverConstants.SLING_SERVLET_METHODS + "=" + HttpConstants.METHOD_GET
})
public class DeletePagesServlet extends SlingSafeMethodsServlet {
@Override
protected void doGet(SlingHttpServletRequest request, @NonNull SlingHttpServletResponse response) throws IOException {
String directoryPath = request.getParameter("directoryPath");
if (isInvalidDirectoryPath(directoryPath, response)) return;
ResourceResolver resourceResolver = request.getResourceResolver();
Resource directoryResource = resourceResolver.getResource(directoryPath);
if (isDirectoryNotFound(directoryResource, response)) return;
try {
assert directoryResource != null;
boolean hasDeleted = deleteNodes(resourceResolver, directoryResource);
if (hasDeleted) {
resourceResolver.commit();
}
sendSuccessResponse(response);
} catch (PersistenceException | RuntimeException e) {
sendErrorResponse(response, e);
}
}
private boolean isInvalidDirectoryPath(String directoryPath, SlingHttpServletResponse response) throws IOException {
if (directoryPath == null || directoryPath.isEmpty()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("Directory path parameter is missing");
return true;
}
return false;
}
private boolean isDirectoryNotFound(Resource directoryResource, SlingHttpServletResponse response) throws IOException {
if (directoryResource == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().write("Directory not found");
return true;
}
return false;
}
private boolean deleteNodes(ResourceResolver resourceResolver, Resource directoryResource) throws PersistenceException {
boolean hasDeleted = false;
for (Resource child : directoryResource.getChildren()) {
ValueMap properties = child.getValueMap();
if (shouldDeleteNode(properties)) {
resourceResolver.delete(child);
hasDeleted = true;
}
}
return hasDeleted;
}
private boolean shouldDeleteNode(ValueMap properties) {
String accountID = properties.get("accountID", String.class);
String mediaId = properties.get("mediaId", String.class);
String playerId = properties.get("playerId", String.class);
String videoId = properties.get("videoId", String.class);
String videoType = properties.get("videoType", String.class);
return accountID != null && isEmpty(mediaId) && isEmpty(playerId) && isEmpty(videoId) && isEmpty(videoType);
}
private boolean isEmpty(String value) {
return value == null || value.isEmpty();
}
private void sendSuccessResponse(SlingHttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write("Nodes deleted successfully");
}
private void sendErrorResponse(SlingHttpServletResponse response, Exception e) throws IOException {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("Commit failed: " + e.getMessage());
}
}
Let’s Understand the code:
9. Imports and Annotations:
- Import necessary classes and annotations.
- Register the servlet as an OSGi service with specific properties.
10. Servlet Class Definition
- Extend
SlingSafeMethodsServlet
for safe HTTP methods like GET. - Override the
doGet
method to handle GET requests.
11. doGet Method:
- Retrieve the directory path from request parameters.
- If the path is invalid, send a
BAD_REQUEST
response. - Use
ResourceResolver
to access the repository and get the directory resource. - If the directory is not found, send a
NOT_FOUND
response. - Call
deleteNodes
to delete nodes within the directory. - If nodes are deleted, commit the changes and send a success response.
- Handle exceptions and send an error response if needed.
12. Methods in the Code:
isInvalidDirectoryPath
: Checks if the directory path is invalid.isDirectoryNotFound
: Checks if the directory resource is not found.deleteNodes
: Iterates through children of the directory resource and deletes nodes based on conditions.shouldDeleteNode
: Checks if a node should be deleted based on its properties.isEmpty
: Utility method to check if a string is null or empty.sendSuccessResponse
: Sends a success response.sendErrorResponse
: Sends an error response with the exception message.
Once your Servlet Code ready, you need to Deploy it in your Local Machine.
To build and deploy only the core module in AEM, you can use the following command:
mvn clean install -PautoInstallBundle
If you want to skip tests during the build, you can add the -DskipTests=true
flag:
mvn clean install -PautoInstallBundle -DskipTests=true
Once Deployed Go and Hit this URL:
localhost:4502/bin/deletenodes?directoryPath=/content/dam
You will get the conformation that Nodes deleted successfully
Every great discussion starts with a simple thought! If you enjoyed this article, found it useful, or have any questions, let’s talk! I’d love to hear from you.
For more updates, tips, and engaging conversations, connect with me on Medium, LinkedIn, and RealCodeWorks. Let’s keep learning together! 🚀✨
Thank you 🙏 !