Skip to content

Instantly share code, notes, and snippets.

@pmedcraft
Last active April 28, 2017 20:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmedcraft/0417179928e34073ee1d3c6a3c844cec to your computer and use it in GitHub Desktop.
Save pmedcraft/0417179928e34073ee1d3c6a3c844cec to your computer and use it in GitHub Desktop.
SDL Web Deployer Extension for sending Purge requests to Akamai
package com.tridion.ps.akamai;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class AkamaiPurgeResponse {
private Integer httpStatus;
private Integer estimatedSeconds;
private Integer pingAfterSeconds;
private String detail;
private String purgeId;
private String progressUri;
private String supportId;
public Integer getHttpStatus() {
return httpStatus;
}
public void setHttpStatus(Integer httpStatus) {
this.httpStatus = httpStatus;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public Integer getEstimatedSeconds() {
return estimatedSeconds;
}
public void setEstimatedSeconds(Integer estimatedSeconds) {
this.estimatedSeconds = estimatedSeconds;
}
public String getPurgeId() {
return purgeId;
}
public void setPurgeId(String purgeId) {
this.purgeId = purgeId;
}
public String getProgressUri() {
return progressUri;
}
public void setProgressUri(String progressUri) {
this.progressUri = progressUri;
}
public Integer getPingAfterSeconds() {
return pingAfterSeconds;
}
public void setPingAfterSeconds(Integer pingAfterSeconds) {
this.pingAfterSeconds = pingAfterSeconds;
}
public String getSupportId() {
return supportId;
}
public void setSupportId(String supportId) {
this.supportId = supportId;
}
}
package com.tridion.ps.akamai;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.tridion.configuration.*;
import com.tridion.deployer.ProcessingException;
import com.tridion.deployer.Processor;
import com.tridion.deployer.Module;
import com.tridion.transport.transportpackage.*;
@SuppressWarnings({"deprecation", "unused"})
public class AkamaiRestFlusher extends Module {
private static Logger log = LoggerFactory.getLogger(AkamaiRestFlusher.class);
private String akamaiUsername = null;
private String akamaiPassword = null;
private String akamaiDomain = null;
private String awaitPurgeCompletion = null;
public AkamaiRestFlusher(Configuration config, Processor processor) throws ConfigurationException {
super(config, processor);
}
// This method is called once for each TransportPackage that is deployed
public void process(TransportPackage data) throws ProcessingException {
log.info("Entering AkamaiFlusher");
try {
akamaiUsername = config.getChild("AkamaiUsername").getContent();
akamaiPassword = config.getChild("AkamaiPassword").getContent();
akamaiDomain = config.getChild("AkamaiDomain").getContent();
awaitPurgeCompletion = config.getChild("AwaitPurgeCompletion").getContent();
} catch(Exception e) {
log.error("Could not get the Akamai username and password. No Akamai flushing.", e);
return;
}
if ((akamaiUsername == null || akamaiUsername.isEmpty()) || (akamaiPassword == null || akamaiPassword.isEmpty())) {
log.error("Akamai user account credentials not provided. No Akamai flushing.");
return;
}
String domainValue = null;
try {
int publicationId = data.getProcessorInstructions().getPublicationId().getItemId();
String domainsPropsFilePath = config.getChild("WebsiteDomains").getContent();
Properties domainProperties = new Properties();
domainProperties.load(new FileReader(new File(domainsPropsFilePath)));
domainValue = domainProperties.getProperty(String.valueOf(publicationId));
} catch (Exception e) {
log.error("Could not get domains for publication. No Akamai flushing.", e);
return;
}
if (domainValue == null || domainValue.isEmpty()) {
log.error("No domain available for publication. No Akamai flushing");
return;
}
List<String> urls = new ArrayList<String>();
String[] domains = domainValue.split(",");
PageMetaData pageFile = (PageMetaData)data.getMetaDataFile("Pages");
if (pageFile != null) {
List<Page> pages = pageFile.getPages();
for ( Page page : pages ) {
for (String domain : domains) {
urls.add(domain + page.getURLPath());
}
}
}
BinaryMetaData binaryFile = (BinaryMetaData)data.getMetaDataFile("Binaries");
if (binaryFile != null) {
List<Binary> binaries = binaryFile.getBinaries();
for ( Binary binary : binaries ) {
String binaryType = binary.getType();
if (binaryType == null || !binaryType.startsWith("image/")) {
for (String domain : domains) {
urls.add(domain + binary.getURLPath());
}
}
}
}
try {
// Now talk to Akamai
purgeUrls(urls);
} catch(Exception e) {
log.error("Error purging URLs [" + e.getMessage() + "]");
throw new ProcessingException("Error purging URLs [" + e.getMessage() + "]");
}
log.info("Completed AkamaiFlusher");
log.info("====================================");
}
private void purgeUrls(List<String> urls) throws ProcessingException {
try {
String domain = "production";
if (akamaiDomain != null && akamaiDomain.equalsIgnoreCase("Staging")) {
domain = "staging";
}
Boolean awaitAkamai = Boolean.TRUE;
if (awaitPurgeCompletion != null && awaitPurgeCompletion.equalsIgnoreCase("False")) {
awaitAkamai = Boolean.FALSE;
}
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("{");
strBuilder.append("\"action\" : \"invalidate\" ,");
strBuilder.append("\"domain\" : \"" + domain + "\" ,");
strBuilder.append("\"objects\": [");
for ( int i=0; i<urls.size(); i++ ) {
String url = urls.get(i);
strBuilder.append("\"" + url + "\"");
if ( i+1<urls.size() ) {
strBuilder.append(",");
}
}
strBuilder.append("]");
strBuilder.append("}");
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(akamaiUsername, akamaiPassword);
provider.setCredentials(AuthScope.ANY, credentials);
HttpClient httpClient = HttpClientBuilder.create().setDefaultCredentialsProvider(provider).build();
HttpPost postRequest = new HttpPost("https://api.ccu.akamai.com/ccu/v2/queues/default");
postRequest.setHeader(new BasicHeader("Content-Type", "application/json"));
postRequest.setHeader(new BasicHeader("Accept", "application/json"));
postRequest.setEntity(new StringEntity(strBuilder.toString()));
HttpResponse response = httpClient.execute(postRequest);
HttpEntity entity = response.getEntity();
ObjectMapper mapper = new ObjectMapper();
AkamaiPurgeResponse akamaiPurgeResp = mapper.readValue(entity.getContent(), AkamaiPurgeResponse.class);
log.info("--------------------------");
log.info("JSON object: " + strBuilder.toString());
log.info("httpStatus: " + akamaiPurgeResp.getHttpStatus());
log.info("detail: " + akamaiPurgeResp.getDetail());
log.info("estimatedSeconds: " + akamaiPurgeResp.getEstimatedSeconds());
log.info("purgeId: " + akamaiPurgeResp.getPurgeId());
log.info("progressUri: " + akamaiPurgeResp.getProgressUri());
log.info("pingAfterSeconds: " + akamaiPurgeResp.getPingAfterSeconds());
log.info("supportId: " + akamaiPurgeResp.getSupportId());
log.info("--------------------------");
if ( !awaitAkamai ) {
log.info("Akamai Flusher currently set to NOT await the purge completion!");
return;
}
Integer waitTimeInSeconds = akamaiPurgeResp.getPingAfterSeconds();
Integer totalWaitingTime = 0;
String progressUri = akamaiPurgeResp.getProgressUri();
boolean purgeComplete = false;
do {
Thread.sleep( waitTimeInSeconds * 1000 );
HttpGet getRequest = new HttpGet("https://api.ccu.akamai.com" + progressUri);
getRequest.setHeader(new BasicHeader("Content-Type", "application/json"));
getRequest.setHeader(new BasicHeader("Accept", "application/json"));
response = httpClient.execute(getRequest);
AkamaiStatusResponse akamaiStatusResp = mapper.readValue(response.getEntity().getContent(), AkamaiStatusResponse.class);
log.info("--------------------------");
log.info("originalEstimatedSeconds: " + akamaiStatusResp.getOriginalEstimatedSeconds());
log.info("progressUri: " + akamaiStatusResp.getProgressUri());
log.info("originalQueueLength: " + akamaiStatusResp.getOriginalQueueLength());
log.info("purgeId: " + akamaiStatusResp.getPurgeId());
log.info("supportId: " + akamaiStatusResp.getSupportId());
log.info("httpStatus: " + akamaiStatusResp.getHttpStatus());
log.info("completionTime: " + akamaiStatusResp.getCompletionTime());
log.info("submittedBy: " + akamaiStatusResp.getSubmittedBy());
log.info("purgeStatus: " + akamaiStatusResp.getPurgeStatus());
log.info("submissionTime:" + akamaiStatusResp.getSubmissionTime());
log.info("pingAfterSeconds: " + akamaiStatusResp.getPingAfterSeconds());
log.info("--------------------------");
if ( akamaiStatusResp.getCompletionTime() == null ) {
totalWaitingTime += waitTimeInSeconds;
if ( totalWaitingTime >= 1800 ) {
throw new ProcessingException("Exceeded maximum waiting time (30 minutes) for purging Akamai");
}
waitTimeInSeconds = akamaiStatusResp.getPingAfterSeconds();
progressUri = akamaiStatusResp.getProgressUri();
} else {
purgeComplete = true;
}
} while ( !purgeComplete );
log.info("************************");
log.info("PURGE REQUEST COMPLETED!");
log.info("************************");
} catch (UnsupportedEncodingException encodingException) {
log.error("Error inside the 'purgeUrls' method [" + encodingException.getMessage() + "]");
throw new ProcessingException("Error purging Akamai [" + encodingException.getMessage() + "]");
} catch (IOException ioException) {
log.error("Error inside the 'purgeUrls' method [" + ioException.getMessage() + "]");
throw new ProcessingException("Error purging Akamai [" + ioException.getMessage() + "]");
} catch (InterruptedException interruptedException) {
log.error("Error inside the 'purgeUrls' method [" + interruptedException.getMessage() + "]");
throw new ProcessingException("Error purging Akamai [" + interruptedException.getMessage() + "]");
}
}
}
package com.tridion.ps.akamai;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class AkamaiStatusResponse extends AkamaiPurgeResponse {
private Integer originalEstimatedSeconds;
private Integer originalQueueLength;
private String completionTime;
private String submittedBy;
private String purgeStatus;
private String submissionTime;
public Integer getOriginalEstimatedSeconds() {
return originalEstimatedSeconds;
}
public void setOriginalEstimatedSeconds(Integer originalEstimatedSeconds) {
this.originalEstimatedSeconds = originalEstimatedSeconds;
}
public Integer getOriginalQueueLength() {
return originalQueueLength;
}
public void setOriginalQueueLength(Integer originalQueueLength) {
this.originalQueueLength = originalQueueLength;
}
public String getCompletionTime() {
return completionTime;
}
public void setCompletionTime(String completionTime) {
this.completionTime = completionTime;
}
public String getSubmittedBy() {
return submittedBy;
}
public void setSubmittedBy(String submittedBy) {
this.submittedBy = submittedBy;
}
public String getPurgeStatus() {
return purgeStatus;
}
public void setPurgeStatus(String purgeStatus) {
this.purgeStatus = purgeStatus;
}
public String getSubmissionTime() {
return submissionTime;
}
public void setSubmissionTime(String submissionTime) {
this.submissionTime = submissionTime;
}
}
<Module Type="CustomAkamaiFlusher" Class="com.tridion.ps.akamai.AkamaiRestFlusher">
<AkamaiUsername>akamaiusername@userdomain.com</AkamaiUsername>
<AkamaiPassword>*************</AkamaiPassword>
<AkamaiDomain>Staging</AkamaiDomain>
<AwaitPurgeCompletion>False</AwaitPurgeCompletion>
<WebsiteDomains>/akamai-flusher/publications-domain.properties</WebsiteDomains>
</Module>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment