Event Handler – Purge CDN Cache

As part of this blog we will try to clear cache with the help of Event Handler on page activation, deactivation and delete. There are various other custom approaches we can follow to purge CDN cache.

Before reading this blog please visit this link to get overall theoretical knowledge around dispatcher, CDN, SSL and how they combinedly works and also this link to see what is required to purge CDN cache using code.

Follow this link if we wish to completely wipe out cache or purge everything using workflow.

Follow below following steps to purge cache for specific page using Event Handler:

Note: Please read comments while going through code will help you to have more understanding around it.

  1. Create custom service which will allow us to clear cache using path:
package com.javadoubts.core.services;

import org.osgi.service.event.Event;

public interface CloudflareCacheClear {

/**
* This will allow us to clear cache using file path
* based on activate, deactivate and delete event.
**/
public void purgeCdnCacheByFile(Event event);

}

2. Implement purgeCdnCacheByFile service java class to purge cache using file path as shown below:

package com.javadoubts.core.services.impl;

import com.day.cq.commons.Externalizer;
import com.javadoubts.core.services.CloudflareCacheClear;
import com.javadoubts.core.services.PracticeService;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.settings.SlingSettingsService;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;

@Component(service = CloudflareCacheClear.class, immediate = true)
public class CloudflareCacheClearImpl implements CloudflareCacheClear {

private static final Logger LOGGER = LoggerFactory.getLogger(CloudflareCacheClearImpl.class);

private static final String PATHS = "paths";

@Reference
private Externalizer externalizer;

@Reference
private SlingSettingsService slingSettingsService;

@Reference
private PracticeService practiceService;

/**
* This will allow us to clear cache by path.
**/
public void purgeCdnCacheByFile(Event event) {
ResourceResolver resolver = practiceService.getResourceResolver();
if (event.containsProperty(PATHS) && null != externalizer) {
String[] paths = (String[]) event.getProperty(PATHS);
for (String path : paths) {
clearCache(resolver, path);
}
}
}

/**
* Create request body having path appended externalizer.
**/
private void clearCache(ResourceResolver resolver, String path) {
// Clear the cache if current page path is starting from /content/practice
if (StringUtils.contains(path, "/content/practice")) {

// Find the resolver mapped shortened URL
String mappedRes = resolver.map(path);

if (null != mappedRes) {
try {
// Create a required JSON object by Cloudflare
JSONObject jsonObject = new JSONObject();
JSONArray jsonArray = new JSONArray();

// Externlize the page URL
jsonArray.put(externalizer.publishLink(resolver, mappedRes));
jsonObject.put("files", jsonArray);

getConnection(jsonObject.toString());
} catch (JSONException e) {
LOGGER.error("JSONException {}", e);
}
}
}
}

/**
* Create connection using API url and authorization
* coming from cloud configuration.
**/
private static void getConnection(String body) {
HttpURLConnection connection = null;

/*
Below apiUrl and authorization bearer token can be
configured as part of system console environment secret and variable.
*/
String apiUrl = "https://api.cloudflare.com/client/v4/zones/asaasasas87d89s97add7a97ds9a79ad/purge_cache";
String authorization = "s90a898da8da9d9d9a9d7a97da89aad";

if (StringUtils.isNotBlank(apiUrl) && StringUtils.isNotBlank(authorization)) {
try {
if (StringUtils.isNotEmpty(body)) {
URL url = new URL(apiUrl);

if (null != url) {
connection = (HttpURLConnection) url.openConnection();
clearCacheConnection(connection, apiUrl, authorization, body);
}
}

} catch (IOException e) {
LOGGER.error("CloudflareCacheClearImpl IOException {} ", e);
} finally {
if (null != connection) {
connection.disconnect();
}
}
}
}

/**
* Make an API call to clear cache.
**/
private static void clearCacheConnection(HttpURLConnection connection,
String apiUrl, String authorization, String body) throws IOException {
if (null != connection) {

LOGGER.error("CloudflareCacheClearImpl inside clearCacheConnection clearCache >>>>>>>>>>");

// Set the request method to POST
connection.setRequestMethod(HttpConstants.METHOD_POST);

// Set headers
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Authorization", "Bearer " + authorization);

// Enable input/output streams
connection.setDoOutput(true);

String encodeUrl = encodeURL(apiUrl);

if (StringUtils.isNotEmpty(encodeUrl)) {
// Write the payload to the connection's output stream
connection.getOutputStream().write(body.getBytes("UTF-8"));

// Get the response code
int responseCode = connection.getResponseCode();
LOGGER.error("Response Code >>>>>> {}", responseCode);

// Read the response
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();

while ((line = reader.readLine()) != null) {
response.append(line);
}

reader.close();

// Print the response
LOGGER.error("Response >>>>>>>>>> {}", response);
}
}
}

private static String encodeURL(String url) throws UnsupportedEncodingException {
return URLEncoder.encode(url, "UTF-8");
}
}

3. Create below event handler to catch an event related to page activation, deactivation or delete which will allow us to purge cache.

package com.javadoubts.core.handler;

import com.day.cq.replication.ReplicationAction;
import com.javadoubts.core.services.CloudflareCacheClear;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

@Component(service = EventHandler.class, property = {
EventConstants.EVENT_TOPIC+"="+ReplicationAction.EVENT_TOPIC }, immediate = true)
public class PurgeCacheEventHandler implements EventHandler {

@Reference
CloudflareCacheClear cloudflareCacheClear;

private static final Logger LOGGER = LoggerFactory.getLogger(PurgeCacheEventHandler.class);

// main logic to handle specific AEM events for iPaas integration
public void handleEvent(Event event) {
LOGGER.debug("handleEvent : starts...");

String eventTopic = (String) event.getProperty(EventConstants.EVENT_TOPIC);
List<String> agents =(List<String>) event.getProperty("agentIds");

// return if event topic is blank or agent type is preview.
if(StringUtils.isBlank(eventTopic) || agents.contains("preview")){
return;
}

// if one of the 'supported' replication events has triggered
// logic to get the page path for replication events
ReplicationAction actionType = ReplicationAction.fromEvent(event);
if (null != actionType) {
cloudflareCacheClear.purgeCdnCacheByFile(event);
}
}
}

4. Create below externalizer configuration to fetch the prod domain in publish:

/ui.config/src/main/content/jcr_root/apps/practice/osgiconfig/config.publish.prod

{
"externalizer.domains": [
"publish www.javadoubts.com"
]
}

Follow link to read more about externalizer.

5. Go to any page and perform one of the page activation, deactivation or delete operation to purge cache.

6. Check error.log file to verify the Cloudflare response:

Response >>>>>>>>>> {“success”:true,”errors”:[],”messages”:[],”result”:{“id”:”aads9ada987d98a79a9d7aa97da9asad”}}

Imran Khan, Adobe Community Advisor, AEM certified developer and Java Geek, is an experienced AEM developer with over 11 years of expertise in designing and implementing robust web applications. He leverages Adobe Experience Manager, Analytics, and Target to create dynamic digital experiences. Imran possesses extensive expertise in J2EE, Sightly, Struts 2.0, Spring, Hibernate, JPA, React, HTML, jQuery, and JavaScript.

0