Custom workflow to purge Cloudflare CDN Cache

As part of this blog we will try to clear cache with the help of custom code. There are various custom approaches we can follow to purge CDN cache.

In this blog we will be discussing below two ways to clear cache in detail:

  1. Purge everything or clear complete cache using workflow.
  2. Follow link to clear cache on publish of content such as page, asset or file.

Before reading this topic 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.

Purge Cache Using Workflow

Follow below following steps to purge cache using workflow:

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:
package com.javadoubts.core.services;


import org.osgi.service.event.Event;

public interface CloudflareCacheClear {

/**
* This method will help us to purge everything.
**/
public void clearCache(String targetUrl);

}

2. Create implementation of above service call to purge cache:

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.servlets.HttpConstants;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
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.
**/
@Override
public void clearCache(String body) {
getConnection(body);
}


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) {
// Open connection
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) {

// 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 workflow process to collect required argument to purge cache and pass same to CloudflareCacheClear service:

package com.javadoubts.core.workflow;

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(service = WorkflowProcess.class,
property = {"process.label=Cloudflare Cache Clear Workflow Process" })
public class CloudflareCacheClearWorkflowProcess implements WorkflowProcess{

@Reference
CloudflareCacheClear cloudflareCacheClear;

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

@Override
public void execute(WorkItem workItem, WorkflowSession wfSession,
MetaDataMap metaDataMap) throws WorkflowException {
LOGGER.debug("CloudflareCacheClearWorkflowProcess entering");
String targetUrl = metaDataMap.get("PROCESS_ARGS", String.class);
if (StringUtils.isNotEmpty(targetUrl)) {
cloudflareCacheClear.clearCache(targetUrl);
}
}
}

4. Create below workflow model to clear cache in below hierarchy and provide PROCESS_ARGS=”\{"purge_everything":true}” as shown below:

/ui.content/src/main/content/jcr_root/conf/practice/settings/workflow/models/cloud-flare-cache-clear

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
jcr:primaryType="cq:Page">
<jcr:content
cq:designPath="/libs/settings/wcm/designs/default"
cq:template="/libs/cq/workflow/templates/model"
jcr:primaryType="cq:PageContent"
jcr:title="Cloud flare Cache Clear"
sling:resourceType="cq/workflow/components/pages/model">
<flow
jcr:primaryType="nt:unstructured"
sling:resourceType="foundation/components/parsys">
<process
jcr:primaryType="nt:unstructured"
jcr:title="Process"
sling:resourceType="cq/workflow/components/model/process">
<metaData
jcr:primaryType="nt:unstructured"
PROCESS="com.javadoubts.core.workflow.CloudflareCacheClearWorkflowProcess"
PROCESS_ARGS="\{&quot;purge_everything&quot;:true}"
PROCESS_AUTO_ADVANCE="true"/>
</process>
</flow>
</jcr:content>
</jcr:root>

5. Create below workflow process sequence:

/ui.content/src/main/content/jcr_root/var/workflow/models

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
jcr:isCheckedOut="{Boolean}false"
jcr:primaryType="cq:WorkflowModel"
jcr:uuid="d69ee330-9e70-40ff-82c0-3bfe034809b1"
sling:resourceType="cq/workflow/components/model"
description="No Description"
title="Cloud flare Cache Clear">
<metaData
cq:generatingPage="/conf/practice/settings/workflow/models/cloud-flare-cache-clear/jcr:content"
jcr:primaryType="nt:unstructured"
lastSynced="{Date}2023-11-28T21:56:19.373+05:30"/>
<nodes jcr:primaryType="nt:unstructured">
<node0
jcr:primaryType="cq:WorkflowNode"
title="Start"
type="START">
<metaData jcr:primaryType="nt:unstructured"/>
</node0>
<node1
jcr:primaryType="cq:WorkflowNode"
title="Process"
type="PROCESS">
<metaData
jcr:primaryType="nt:unstructured"
PROCESS="com.javadoubts.core.workflow.CloudflareCacheClearWorkflowProcess"/>
</node1>
<node2
jcr:primaryType="cq:WorkflowNode"
title="End"
type="END">
<metaData jcr:primaryType="nt:unstructured"/>
</node2>
</nodes>
<transitions jcr:primaryType="nt:unstructured">
<node0_x0023_node1
jcr:primaryType="cq:WorkflowTransition"
from="node0"
rule="\0"
to="node1">
<metaData jcr:primaryType="nt:unstructured"/>
</node0_x0023_node1>
<node1_x0023_node2
jcr:primaryType="cq:WorkflowTransition"
from="node1"
to="node2">
<metaData jcr:primaryType="nt:unstructured"/>
</node1_x0023_node2>
</transitions>
</jcr:root>

6. Made below entry as part of filter.xml file under ui.content module:

<filter root="/conf/practice/settings/workflow/"/>
<filter root="/var/workflow/models/cloud-flare-cache-clear"/>

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

Verify changes on AEM instance post build:

Trigger a build and verify the changes got successfully deployed on AEM instance using below steps:

  1. Go to sites.html page and click on tools. Traverse to workflow section and click on Models tile as shown below:

2. Select workflow mode and click on edit button:

3. Verify if model is synced or not. If not, please click on Sync button highlighted below:

Execute Workflow to Purge Cache

Follow below steps to execute workflow and purge CDN cache:

  1. Open any page and select page information icon. Select Start Workflow option under it as shown below:

2. Select workflow model, provide name and than click on Start Workflow will actually start our workflow.

3. Now, go to sites.html page and click on tools. Traverse to workflow section and click on Archive option highlighted below to see the successful execution of workflow.

Else, check in Instances or Failures for currently running or failed workflows.

4. Below is the archive view of successful workflow execution:

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