Skip to content

Commit

Permalink
Converted Flowdock XML template to Java template
Browse files Browse the repository at this point in the history
Fixes #15

Added ability to use ${sanitise(varName)} or ${sanitize(varName)} in a
template
Fixes #17

Added new project (tcwebhooks-template-builder) to build some templates
from XML.
  • Loading branch information
netwolfuk committed Nov 15, 2015
1 parent 332d7a5 commit b595471
Show file tree
Hide file tree
Showing 18 changed files with 798 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package webhook.teamcity.payload.template;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import webhook.teamcity.BuildStateEnum;
import webhook.teamcity.Loggers;
import webhook.teamcity.payload.WebHookTemplateContent;
import webhook.teamcity.payload.WebHookTemplateManager;

public abstract class AbstractFileSetBasedWebHookTemplate extends AbstractWebHookTemplate {

Map<BuildStateEnum,WebHookTemplateContent> templateContent = new HashMap<BuildStateEnum, WebHookTemplateContent>();
Map<BuildStateEnum,WebHookTemplateContent> branchTemplateContent = new HashMap<BuildStateEnum, WebHookTemplateContent>();

public abstract String getLoggingName();
public abstract String getTemplateFilesLocation();

public abstract Map<BuildStateEnum, String> getNormalTemplateMap();
public abstract Map<BuildStateEnum, String> getBranchTemplateMap();

public AbstractFileSetBasedWebHookTemplate(WebHookTemplateManager manager) {
setTemplateManager(manager);
}

@Override
public void register() {
templateContent.clear();
branchTemplateContent.clear();
loadTemplatesFromFileSet();
if (!templateContent.isEmpty() && !branchTemplateContent.isEmpty()){
super.register(this);
} else {
if (templateContent.isEmpty()){
Loggers.SERVER.error(getLoggingName() + " :: Failed to register template " + getTemplateShortName() + ". No regular template configurations were found.");
}
if (branchTemplateContent.isEmpty()){
Loggers.SERVER.error(getLoggingName() + " :: Failed to register template " + getTemplateShortName() + ". No branch template configurations were found.");
}
}
}

private URL findPropertiesFileUrlInVariousClassloaders(String propertiesFile) {
final ClassLoader[] classLoaders = {AbstractFileSetBasedWebHookTemplate.class.getClassLoader(), ClassLoader.getSystemClassLoader()};
URL url = null;
for (ClassLoader cl : classLoaders){
if (cl != null){
url = cl.getResource(propertiesFile);
if (url != null){
break;
}
}
}
return url;
}

private static final int BUFFER_SIZE = 4 * 1024;

public static String inputStreamToString(InputStream inputStream, String charsetName)
throws IOException {
StringBuilder builder = new StringBuilder();
InputStreamReader reader = new InputStreamReader(inputStream, charsetName);
char[] buffer = new char[BUFFER_SIZE];
int length;
while ((length = reader.read(buffer)) != -1) {
builder.append(buffer, 0, length);
}
return builder.toString();
}

public String readFully(InputStream inputStream, String encoding)
throws IOException {
return new String(readFully(inputStream), encoding);
}

private byte[] readFully(InputStream inputStream)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
}

private String readFileContents(String templateFile){
URL url = findPropertiesFileUrlInVariousClassloaders(templateFile);
if (url != null) {
InputStream in = null;
try {
in = url.openStream();
return inputStreamToString(in, "UTF-8");
} catch (IOException e) {
Loggers.SERVER.error(getLoggingName() + " :: An Error occurred trying to load the template file: " + templateFile + ".");
Loggers.SERVER.debug(e);

} finally {
try {
if (in != null){
in.close();
}
} catch (IOException e) {
Loggers.SERVER.error(e);
}
}
} else {
Loggers.SERVER.error(getLoggingName() + " :: An Error occurred trying to load the template file: " + templateFile + ". The file was not found in the classpath.");
}
return null;
}

/**
* Load the template from a file, rather than doing silly string escaping in java.
*/
private void loadTemplatesFromFileSet() {
for (BuildStateEnum state : BuildStateEnum.getNotifyStates()){
if (getNormalTemplateMap().containsKey(state)){
String templateContents = readFileContents(getTemplateFilesLocation() + File.separator + getNormalTemplateMap().get(state));
if (templateContents != null) {
templateContent.put(state, WebHookTemplateContent.create(
state.getShortName(),
templateContents,
true,
this.getPreferredDateTimeFormat()));
Loggers.SERVER.info(getLoggingName() + " :: Found and loaded normal template for: " + state.getShortName());
Loggers.SERVER.debug(getLoggingName() + " :: Template content is: " + templateContents);

}
}
}
for (BuildStateEnum state : BuildStateEnum.getNotifyStates()){
if (getBranchTemplateMap().containsKey(state)){
String templateContents = readFileContents(getTemplateFilesLocation() + File.separator + getBranchTemplateMap().get(state));
if (templateContents != null) {
branchTemplateContent.put(state, WebHookTemplateContent.create(
state.getShortName(),
templateContents,
true,
this.getPreferredDateTimeFormat()));
Loggers.SERVER.info(getLoggingName() + " :: Found and loaded branch template for: " + state.getShortName());
Loggers.SERVER.debug(getLoggingName() + " :: Template content is: " + templateContents);

}
}
}
}

@Override
public WebHookTemplateContent getTemplateForState(BuildStateEnum buildState) {
if (templateContent.containsKey(buildState)){
return (templateContent.get(buildState)).copy();
}
return null;
}

@Override
public WebHookTemplateContent getBranchTemplateForState(BuildStateEnum buildState) {
if (branchTemplateContent.containsKey(buildState)){
return (branchTemplateContent.get(buildState)).copy();
}
return null;
}

@Override
public Set<BuildStateEnum> getSupportedBuildStates() {
return templateContent.keySet();
}

@Override
public Set<BuildStateEnum> getSupportedBranchBuildStates() {
return branchTemplateContent.keySet();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package webhook.teamcity.payload.template;

import java.util.Map;
import java.util.TreeMap;

import webhook.teamcity.BuildStateEnum;
import webhook.teamcity.payload.WebHookTemplateManager;
import webhook.teamcity.payload.format.WebHookPayloadJsonTemplate;

public class FlowdockWebHookTemplate extends AbstractFileSetBasedWebHookTemplate {

private Map<BuildStateEnum, String> normalTemplateMap = new TreeMap<BuildStateEnum, String>();
private Map<BuildStateEnum, String> branchTemplateMap = new TreeMap<BuildStateEnum, String>();

public FlowdockWebHookTemplate(WebHookTemplateManager manager) {
super(manager);

normalTemplateMap.put(BuildStateEnum.BUILD_BROKEN, "flowdock-buildBroken-buildFailed-buildInterrupted-normal.json");
normalTemplateMap.put(BuildStateEnum.BUILD_FAILED, "flowdock-buildBroken-buildFailed-buildInterrupted-normal.json");
normalTemplateMap.put(BuildStateEnum.BUILD_INTERRUPTED, "flowdock-buildBroken-buildFailed-buildInterrupted-normal.json");
normalTemplateMap.put(BuildStateEnum.BUILD_STARTED, "flowdock-buildStarted-normal.json");
normalTemplateMap.put(BuildStateEnum.BUILD_SUCCESSFUL, "flowdock-buildSuccessful-buildFixed-normal.json");
normalTemplateMap.put(BuildStateEnum.BUILD_FIXED, "flowdock-buildSuccessful-buildFixed-normal.json");
normalTemplateMap.put(BuildStateEnum.RESPONSIBILITY_CHANGED, "flowdock-responsibilityChanged-normal.json");

branchTemplateMap.put(BuildStateEnum.BUILD_BROKEN, "flowdock-buildBroken-buildFailed-buildInterrupted-branch.json");
branchTemplateMap.put(BuildStateEnum.BUILD_FAILED, "flowdock-buildBroken-buildFailed-buildInterrupted-branch.json");
branchTemplateMap.put(BuildStateEnum.BUILD_INTERRUPTED, "flowdock-buildBroken-buildFailed-buildInterrupted-branch.json");
branchTemplateMap.put(BuildStateEnum.BUILD_STARTED, "flowdock-buildStarted-branch.json");
branchTemplateMap.put(BuildStateEnum.BUILD_SUCCESSFUL, "flowdock-buildSuccessful-buildFixed-branch.json");
branchTemplateMap.put(BuildStateEnum.BUILD_FIXED, "flowdock-buildSuccessful-buildFixed-branch.json");
branchTemplateMap.put(BuildStateEnum.RESPONSIBILITY_CHANGED, "flowdock-responsibilityChanged-branch.json");

}

@Override
public String getTemplateDescription() {
return "Flowdock JSON templates";
}

@Override
public String getTemplateToolTipText() {
return "Supports the TeamCity Flowdock JSON integration";
}

@Override
public String getTemplateShortName() {
return "flowdock";
}

@Override
public boolean supportsPayloadFormat(String payloadFormat) {
return payloadFormat.equalsIgnoreCase(WebHookPayloadJsonTemplate.FORMAT_SHORT_NAME);
}

@Override
public String getPreferredDateTimeFormat() {
return "";
}

@Override
public String getLoggingName() {
return this.getClass().getSimpleName();
}

@Override
public String getTemplateFilesLocation() {
return "webhook/teamcity/payload/template/flowdock";
}

@Override
public Map<BuildStateEnum, String> getNormalTemplateMap() {
return this.normalTemplateMap;
}

@Override
public Map<BuildStateEnum, String> getBranchTemplateMap() {
return this.branchTemplateMap;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package webhook.teamcity.payload.util;

public class StringSanitiser {

public static String sanitise(String dirtyString) {
return dirtyString
.replace("<", "_")
.replace(">", "_")
.replace("\\", "_")
.replace("/", "_")
.replace("$", "_")
.replace("%", "_")
.replace("#", "_")
.replace("@", "_")
.replace("!", "_")
.replace("`", "_")
.replace("~", "_")
.replace("?", "_")
.replace("|", "_")
.replace("*", "_")
.replace("(", "_")
.replace(")", "_")
.replace("^", "_")
;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,24 @@ public String resolve(String variableName) {
// do nothing and let the logic below handle it.
}
}

if ((variableName.startsWith("sanitise(") || variableName.startsWith("sanitize(")) && variableName.endsWith(")")){
try {
String dirtyString = variableName.substring("sanitise(".length(), variableName.length() - ")".length());
if (teamcityProperties.containsKey(dirtyString)){
return StringSanitiser.sanitise(teamcityProperties.get(dirtyString));
} else {
return StringSanitiser.sanitise((String) PropertyUtils.getProperty(bean, dirtyString).toString());
}

// do nothing and let the logic below handle it.
} catch (NullPointerException npe){
} catch (IllegalArgumentException iae){
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
} catch (NoSuchMethodException e) {
}
}

try {
// Try getting it from teamcity first.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"event": "activity",
"author": {
"name": "TeamCity",
"avatar": "https://raw.githubusercontent.com/tcplugins/tcWebHooks/custom-templates/tcwebhooks-core/src/docs/images/buildFailed%402x.png"
},
"title": "updated build status to ${notifyType}",
"external_thread_id": "teamcity-${buildName}-${sanitize(branchName)}",
"tags": [ "#${buildExternalTypeId}", "#${projectExternalId}", "#${notifyType}", "#${branchDisplayName}", "#teamcity" ],
"thread": {
"title": "${buildName} (${sanitize(branchDisplayName)})",
"fields": [
{ "label": "Build", "value": "${buildFullName}" },
{ "label": "Branch", "value": "${branchDisplayName}" },
{ "label": "Default Branch", "value": "${branchIsDefault}" },
{ "label": "Triggered By", "value": "${triggeredBy}" },
{ "label": "Agent", "value": "${agentName}" }
],
"body": "<a href=\"${rootUrl}/project.html?projectId=${projectId}\">${projectName}</a> :: <a href=\"${rootUrl}/viewType.html?buildTypeId=${buildTypeId}\">${buildName}</a> # <a href=\"${rootUrl}/viewLog.html?buildTypeId=${buildTypeId}&buildId=${buildId}\"><strong>${buildNumber}</strong></a> has <strong>${buildStateDescription}</strong> with a status of <a href=\"${rootUrl}/viewLog.html?buildTypeId=${buildTypeId}&buildId=${buildId}\"><strong>${buildResult}</strong></a>",
"external_url": "${buildStatusUrl}",
"status": {
"color": "red",
"value": "${buildResult}"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"event": "activity",
"author": {
"name": "TeamCity",
"avatar": "https://raw.githubusercontent.com/tcplugins/tcWebHooks/custom-templates/tcwebhooks-core/src/docs/images/buildFailed%402x.png"
},
"title": "updated build status to ${notifyType}",
"external_thread_id": "teamcity-${buildName}",
"tags": [ "#${buildExternalTypeId}", "#${projectExternalId}", "#${notifyType}", "#teamcity" ],
"thread": {
"title": "${buildName}",
"fields": [
{ "label": "Build", "value": "${buildFullName}" },
{ "label": "Triggered By", "value": "${triggeredBy}" },
{ "label": "Agent", "value": "${agentName}" }
],
"body": "<a href=\"${rootUrl}/project.html?projectId=${projectId}\">${projectName}</a> :: <a href=\"${rootUrl}/viewType.html?buildTypeId=${buildTypeId}\">${buildName}</a> # <a href=\"${rootUrl}/viewLog.html?buildTypeId=${buildTypeId}&buildId=${buildId}\"><strong>${buildNumber}</strong></a> has <strong>${buildStateDescription}</strong> with a status of <a href=\"${rootUrl}/viewLog.html?buildTypeId=${buildTypeId}&buildId=${buildId}\"><strong>${buildResult}</strong></a>",
"external_url": "${buildStatusUrl}",
"status": {
"color": "red",
"value": "${buildResult}"
}
}
}
Loading

0 comments on commit b595471

Please sign in to comment.