Last active
April 28, 2017 20:59
-
-
Save pmedcraft/0417179928e34073ee1d3c6a3c844cec to your computer and use it in GitHub Desktop.
SDL Web Deployer Extension for sending Purge requests to Akamai
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() + "]"); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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