/* $Id$ */
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.manifoldcf.crawler.notifications.slack;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.manifoldcf.agents.interfaces.ServiceInterruption;
import org.apache.manifoldcf.core.interfaces.ConfigParams;
import org.apache.manifoldcf.core.interfaces.IHTTPOutput;
import org.apache.manifoldcf.core.interfaces.IPasswordMapperActivity;
import org.apache.manifoldcf.core.interfaces.IPostParameters;
import org.apache.manifoldcf.core.interfaces.IThreadContext;
import org.apache.manifoldcf.core.interfaces.ManifoldCFException;
import org.apache.manifoldcf.core.interfaces.Specification;
import org.apache.manifoldcf.core.interfaces.SpecificationNode;
import org.apache.manifoldcf.crawler.system.Logging;
/**
*/
public class SlackConnector extends org.apache.manifoldcf.crawler.notifications.BaseNotificationConnector {
protected final static long SESSION_EXPIRATION_MILLISECONDS = 300000L;
// Local variables.
protected long sessionExpiration = -1L;
// Parameters for establishing a session
protected String webHookUrl = null;
// Parameters for proxy connection
protected SlackSession.ProxySettings proxySettings = null;
// Local session handle
protected SlackSession session = null;
//////////////////////////////////Start of Basic Connector Methods/////////////////////////
/**
* Connect.
*
* @param configParameters is the set of configuration parameters, which
* in this case describe the root directory.
*/
@Override
public void connect(ConfigParams configParameters) {
super.connect(configParameters);
this.webHookUrl = configParameters.getObfuscatedParameter(SlackConfig.WEBHOOK_URL_PARAM);
String proxyHost = configParameters.getParameter(SlackConfig.PROXY_HOST_PARAM);
String proxyPortString = configParameters.getParameter(SlackConfig.PROXY_PORT_PARAM);
if(StringUtils.isNotEmpty(proxyHost) && StringUtils.isNotEmpty(proxyPortString)) {
String proxyUsername = configParameters.getParameter(SlackConfig.PROXY_USERNAME_PARAM);
String proxyPassword = configParameters.getObfuscatedParameter(SlackConfig.PROXY_PASSWORD_PARAM);
String proxyDomain = configParameters.getParameter(SlackConfig.PROXY_DOMAIN_PARAM);
this.proxySettings = new SlackSession.ProxySettings(proxyHost, proxyPortString, proxyUsername, proxyPassword, proxyDomain);
} else {
Logging.connectors.info("Using no proxy settings - no proxyHost and no proxyPort found.");
}
}
/**
* Close the connection. Call this before discarding this instance of the
* repository connector.
*/
@Override
public void disconnect()
throws ManifoldCFException {
this.webHookUrl = null;
this.proxySettings = null;
finalizeConnection();
super.disconnect();
}
/**
* This method is periodically called for all connectors that are connected but not
* in active use.
*/
@Override
public void poll() throws ManifoldCFException {
if (session != null)
{
if (System.currentTimeMillis() >= sessionExpiration)
finalizeConnection();
}
}
/**
* Test the connection. Returns a string describing the connection integrity.
*
* @return the connection's status as a displayable string.
*/
@Override
public String check()
throws ManifoldCFException {
try {
checkConnection();
return super.check();
} catch (ServiceInterruption e) {
return "Connection temporarily failed: " + e.getMessage();
} catch (ManifoldCFException e) {
return "Connection failed: " + e.getMessage();
}
}
protected void checkConnection() throws ManifoldCFException, ServiceInterruption {
// Force a re-connection
finalizeConnection();
getSession();
try {
CheckConnectionThread cct = new CheckConnectionThread(session);
cct.start();
cct.finishUp();
} catch (InterruptedException e) {
throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
} catch (IOException e) {
handleIOException(e,"checking the connection");
}
}
protected void getSession()
throws ManifoldCFException, ServiceInterruption {
if (session == null) {
// Check that all the required parameters are there.
if (webHookUrl == null)
throw new ManifoldCFException("Missing webHookUrl parameter");
// Create a session.
try {
ConnectThread connectThread = new ConnectThread(webHookUrl, proxySettings);
connectThread.start();
session = connectThread.finishUp();
} catch (InterruptedException e) {
throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
} catch (IOException e) {
handleIOException(e, "connecting");
}
}
sessionExpiration = System.currentTimeMillis() + SESSION_EXPIRATION_MILLISECONDS;
}
protected void finalizeConnection() {
if (session != null) {
try {
CloseSessionThread closeSessionThread = new CloseSessionThread(session);
closeSessionThread.start();
closeSessionThread.finishUp();
} catch (InterruptedException e) {
} catch (IOException e) {
Logging.connectors.warn("Error while closing connection to server: " + e.getMessage(),e);
} finally {
session = null;
}
}
}
///////////////////////////////End of Basic Connector Methods////////////////////////////////////////
//////////////////////////////Start of Notification Connector Method///////////////////////////////////
/** Notify of job stop due to error abort.
*@param spec is the notification specification.
*/
@Override
public void notifyOfJobStopErrorAbort(final Specification spec)
throws ManifoldCFException, ServiceInterruption {
sendSlackMessage(spec, SlackConfig.NODE_ERRORABORTED);
}
/** Notify of job stop due to manual abort.
*@param spec is the notification specification.
*/
@Override
public void notifyOfJobStopManualAbort(final Specification spec)
throws ManifoldCFException, ServiceInterruption {
sendSlackMessage(spec, SlackConfig.NODE_MANUALABORTED);
}
/** Notify of job stop due to manual pause.
*@param spec is the notification specification.
*/
@Override
public void notifyOfJobStopManualPause(final Specification spec)
throws ManifoldCFException, ServiceInterruption {
sendSlackMessage(spec, SlackConfig.NODE_MANUALPAUSED);
}
/** Notify of job stop due to schedule pause.
*@param spec is the notification specification.
*/
@Override
public void notifyOfJobStopSchedulePause(final Specification spec)
throws ManifoldCFException, ServiceInterruption {
sendSlackMessage(spec, SlackConfig.NODE_SCHEDULEPAUSED);
}
/** Notify of job stop due to restart.
*@param spec is the notification specification.
*/
@Override
public void notifyOfJobStopRestart(final Specification spec)
throws ManifoldCFException, ServiceInterruption {
sendSlackMessage(spec, SlackConfig.NODE_RESTARTED);
}
/** Notify of job end.
*@param spec is the notification specification.
*/
@Override
public void notifyOfJobEnd(final Specification spec)
throws ManifoldCFException, ServiceInterruption {
sendSlackMessage(spec, SlackConfig.NODE_FINISHED);
}
protected void sendSlackMessage(final Specification spec, final String nodeType)
throws ManifoldCFException, ServiceInterruption
{
String channel = "";
String message = "";
for (int i = 0; i < spec.getChildCount(); i++) {
SpecificationNode sn = spec.getChild(i);
if (sn.getType().equals(SlackConfig.NODE_CHANNEL))
channel = sn.getAttributeValue(SlackConfig.ATTRIBUTE_VALUE);
else if (sn.getType().equals(SlackConfig.NODE_MESSAGE))
message = sn.getAttributeValue(SlackConfig.ATTRIBUTE_VALUE);
}
// Look for node of the specified type
if (nodeType != null)
{
for (int i = 0; i < spec.getChildCount(); i++) {
SpecificationNode childNode = spec.getChild(i);
if (childNode.getType().equals(nodeType))
{
for (int j = 0; j < childNode.getChildCount(); j++) {
SpecificationNode sn = childNode.getChild(j);
if (sn.getType().equals(SlackConfig.NODE_CHANNEL))
channel = sn.getAttributeValue(SlackConfig.ATTRIBUTE_VALUE);
else if (sn.getType().equals(SlackConfig.NODE_MESSAGE))
message = sn.getAttributeValue(SlackConfig.ATTRIBUTE_VALUE);
}
}
}
}
if (StringUtils.isBlank(message)) {
return;
}
// Construct and send a slack message
getSession();
SendThread st = new SendThread(session, channel, message);
st.start();
try {
st.finishUp();
} catch (InterruptedException e) {
throw new ManifoldCFException(e.getMessage(),ManifoldCFException.INTERRUPTED);
} catch (IOException e) {
handleIOException(e,"sending slack message");
}
}
//////////////////////////////End of Notification Connector Methods///////////////////////////////////
///////////////////////////////////////Start of Configuration UI/////////////////////////////////////
/**
* Output the configuration header section.
* This method is called in the head section of the connector's configuration page. Its purpose is to
* add the required tabs to the list, and to output any javascript methods that might be needed by
* the configuration editing HTML.
* The connector does not need to be connected for this method to be called.
*
* @param threadContext is the local thread context.
* @param out is the output to which any HTML should be sent.
* @param locale is the desired locale.
* @param parameters are the configuration parameters, as they currently exist, for this connection being configured.
* @param tabsArray is an array of tab names. Add to this array any tab names that are specific to the connector.
*/
@Override
public void outputConfigurationHeader(IThreadContext threadContext, IHTTPOutput out,
Locale locale, ConfigParams parameters, List<String> tabsArray)
throws ManifoldCFException, IOException {
tabsArray.add(Messages.getString(locale, "SlackConnector.WebHook"));
// Map the parameters
Map<String, Object> paramMap = new HashMap<String, Object>();
// Fill in the parameters from each tab
fillInServerConfigurationMap(paramMap, out, parameters);
// Output the Javascript - only one Velocity template for all tabs
Messages.outputResourceWithVelocity(out, locale, "ConfigurationHeader.js", paramMap);
}
@Override
public void outputConfigurationBody(IThreadContext threadContext, IHTTPOutput out,
Locale locale, ConfigParams parameters, String tabName)
throws ManifoldCFException, IOException {
// Output the Server tab
Map<String, Object> paramMap = new HashMap<String, Object>();
// Set the tab name
paramMap.put("TabName", tabName);
// Fill in the parameters
fillInServerConfigurationMap(paramMap, out, parameters);
Messages.outputResourceWithVelocity(out, locale, "Configuration_Server.html", paramMap);
}
private static void fillInServerConfigurationMap(Map<String, Object> paramMap, IPasswordMapperActivity mapper, ConfigParams parameters) {
String webHookUrl = parameters.getObfuscatedParameter(SlackConfig.WEBHOOK_URL_PARAM);
if (webHookUrl == null) {
webHookUrl = StringUtils.EMPTY;
} else {
webHookUrl = mapper.mapPasswordToKey(webHookUrl);
}
String proxyHost = getEmptyOnNull(parameters, SlackConfig.PROXY_HOST_PARAM);
String proxyPort = getEmptyOnNull(parameters, SlackConfig.PROXY_PORT_PARAM);
String proxyUsername = getEmptyOnNull(parameters, SlackConfig.PROXY_USERNAME_PARAM);
String proxyPassword = parameters.getObfuscatedParameter(SlackConfig.PROXY_PASSWORD_PARAM);
if(proxyPassword == null) {
proxyPassword = StringUtils.EMPTY;
} else {
proxyPassword = mapper.mapPasswordToKey(proxyPassword);
}
String proxyDomain = getEmptyOnNull(parameters, SlackConfig.PROXY_DOMAIN_PARAM);
paramMap.put("WEBHOOK_URL", webHookUrl);
paramMap.put("PROXY_HOST", proxyHost);
paramMap.put("PROXY_PORT", proxyPort);
paramMap.put("PROXY_USERNAME", proxyUsername);
paramMap.put("PROXY_PASSWORD", proxyPassword);
paramMap.put("PROXY_DOMAIN", proxyDomain);
}
private static String getEmptyOnNull(ConfigParams parameters, String key) {
String value = parameters.getParameter(key);
if (value == null) {
value = StringUtils.EMPTY;
}
return value;
}
/**
* Process a configuration post.
* This method is called at the start of the connector's configuration page, whenever there is a possibility
* that form data for a connection has been posted. Its purpose is to gather form information and modify
* the configuration parameters accordingly.
* The name of the posted form is always "editconnection".
* The connector does not need to be connected for this method to be called.
*
* @param threadContext is the local thread context.
* @param variableContext is the set of variables available from the post, including binary file post information.
* @param parameters are the configuration parameters, as they currently exist, for this connection being configured.
* @return null if all is well, or a string error message if there is an error that should prevent saving of the
* connection (and cause a redirection to an error page).
*/
@Override
public String processConfigurationPost(IThreadContext threadContext, IPostParameters variableContext,
ConfigParams parameters) throws ManifoldCFException {
final String webHookUrl = variableContext.getParameter("webHookUrl");
if (webHookUrl != null) {
parameters.setObfuscatedParameter(SlackConfig.WEBHOOK_URL_PARAM, variableContext.mapKeyToPassword(webHookUrl));
}
final String proxyHost = variableContext.getParameter("proxyHost");
if (proxyHost != null) {
parameters.setParameter(SlackConfig.PROXY_HOST_PARAM, proxyHost);
}
final String proxyPort = variableContext.getParameter("proxyPort");
if (StringUtils.isNotEmpty(proxyPort)) {
try {
Integer.parseInt(proxyPort);
} catch (NumberFormatException e) {
Logging.connectors.warn("Proxy port must be a number. Found " + proxyPort);
throw new ManifoldCFException("Proxy Port must be a number: " + e.getMessage(), e);
}
parameters.setParameter(SlackConfig.PROXY_PORT_PARAM, proxyPort);
} else if(proxyPort != null){
parameters.setParameter(SlackConfig.PROXY_PORT_PARAM, proxyPort);
}
final String proxyUsername = variableContext.getParameter("proxyUsername");
if (proxyUsername != null) {
parameters.setParameter(SlackConfig.PROXY_USERNAME_PARAM, proxyUsername);
}
final String proxyPassword = variableContext.getParameter("proxyPassword");
if (proxyPassword != null) {
parameters.setObfuscatedParameter(SlackConfig.PROXY_PASSWORD_PARAM, variableContext.mapKeyToPassword(proxyPassword));
}
final String proxyDomain = variableContext.getParameter("proxyDomain");
if (proxyDomain != null) {
parameters.setParameter(SlackConfig.PROXY_DOMAIN_PARAM, proxyDomain);
}
return null;
}
/**
* View configuration. This method is called in the body section of the
* connector's view configuration page. Its purpose is to present the
* connection information to the user. The coder can presume that the HTML that
* is output from this configuration will be within appropriate <html> and
* <body> tags.
*
* @param threadContext is the local thread context.
* @param out is the output to which any HTML should be sent.
* @param parameters are the configuration parameters, as they currently exist, for
* this connection being configured.
*/
@Override
public void viewConfiguration(IThreadContext threadContext, IHTTPOutput out,
Locale locale, ConfigParams parameters) throws ManifoldCFException, IOException {
Map<String, Object> paramMap = new HashMap<String, Object>();
// Fill in map from each tab
fillInServerConfigurationMap(paramMap, out, parameters);
Messages.outputResourceWithVelocity(out, locale, "ConfigurationView.html", paramMap);
}
/////////////////////////////////End of configuration UI////////////////////////////////////////////////////
/////////////////////////////////Start of Specification UI//////////////////////////////////////////////////
/** Output the specification header section.
* This method is called in the head section of a job page which has selected a repository connection of the
* current type. Its purpose is to add the required tabs to the list, and to output any javascript methods
* that might be needed by the job editing HTML.
* The connector will be connected before this method can be called.
*@param out is the output to which any HTML should be sent.
*@param locale is the locale the output is preferred to be in.
*@param ds is the current document specification for this job.
*@param connectionSequenceNumber is the unique number of this connection within the job.
*@param tabsArray is an array of tab names. Add to this array any tab names that are specific to the connector.
*/
@Override
public void outputSpecificationHeader(IHTTPOutput out, Locale locale, Specification ds,
int connectionSequenceNumber, List<String> tabsArray)
throws ManifoldCFException, IOException {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("SeqNum", Integer.toString(connectionSequenceNumber));
// Add the tabs
tabsArray.add(Messages.getString(locale, "SlackConnector.Message"));
Messages.outputResourceWithVelocity(out, locale, "SpecificationHeader.js", paramMap);
}
/** Output the specification body section.
* This method is called in the body section of a job page which has selected a repository connection of the
* current type. Its purpose is to present the required form elements for editing.
* The coder can presume that the HTML that is output from this configuration will be within appropriate
* <html>, <body>, and <form> tags. The name of the form is always "editjob".
* The connector will be connected before this method can be called.
*@param out is the output to which any HTML should be sent.
*@param locale is the locale the output is preferred to be in.
*@param ds is the current document specification for this job.
*@param connectionSequenceNumber is the unique number of this connection within the job.
*@param actualSequenceNumber is the connection within the job that has currently been selected.
*@param tabName is the current tab name. (actualSequenceNumber, tabName) form a unique tuple within
* the job.
*/
@Override
public void outputSpecificationBody(IHTTPOutput out, Locale locale, Specification ds,
int connectionSequenceNumber, int actualSequenceNumber, String tabName)
throws ManifoldCFException, IOException {
outputMessageTab(out, locale, ds, tabName, connectionSequenceNumber, actualSequenceNumber);
}
/**
* Take care of "Message" tab.
*/
protected void outputMessageTab(IHTTPOutput out, Locale locale,
Specification ds, String tabName, int connectionSequenceNumber, int actualSequenceNumber)
throws ManifoldCFException, IOException {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("TabName", tabName);
paramMap.put("SeqNum", Integer.toString(connectionSequenceNumber));
paramMap.put("SelectedNum", Integer.toString(actualSequenceNumber));
fillInMessageTab(paramMap, ds);
Messages.outputResourceWithVelocity(out, locale, "Specification_Message.html", paramMap);
}
/**
* Fill in Velocity context for Metadata tab.
*/
protected static void fillInMessageTab(Map<String, Object> paramMap,
Specification ds) {
// Preload default values, for backwards compatibility
String channel = "";
String message = "";
for (int i = 0; i < ds.getChildCount(); i++) {
SpecificationNode sn = ds.getChild(i);
if (sn.getType().equals(SlackConfig.NODE_CHANNEL)) {
channel = sn.getAttributeValue(SlackConfig.ATTRIBUTE_VALUE);
} else if (sn.getType().equals(SlackConfig.NODE_MESSAGE)) {
message = sn.getAttributeValue(SlackConfig.ATTRIBUTE_VALUE);
}
}
// If ANY of the above are non-empty, we create a new dummy record
if (channel.length() > 0) {
// Add the dummy records
addRecord(paramMap, SlackConfig.NODE_FINISHED, channel, message);
addRecord(paramMap, SlackConfig.NODE_ERRORABORTED, channel, message);
addRecord(paramMap, SlackConfig.NODE_MANUALABORTED, channel, message);
addRecord(paramMap, SlackConfig.NODE_MANUALPAUSED, channel, message);
addRecord(paramMap, SlackConfig.NODE_SCHEDULEPAUSED, channel, message);
addRecord(paramMap, SlackConfig.NODE_RESTARTED, channel, message);
}
else
{
// Initialize all records with blanks
addRecord(paramMap, SlackConfig.NODE_FINISHED, "", "");
addRecord(paramMap, SlackConfig.NODE_ERRORABORTED, "", "");
addRecord(paramMap, SlackConfig.NODE_MANUALABORTED, "", "");
addRecord(paramMap, SlackConfig.NODE_MANUALPAUSED, "", "");
addRecord(paramMap, SlackConfig.NODE_SCHEDULEPAUSED, "", "");
addRecord(paramMap, SlackConfig.NODE_RESTARTED, "" ,"");
// Loop through nodes and pick them out that way
for (int i = 0; i < ds.getChildCount(); i++) {
SpecificationNode childNode = ds.getChild(i);
if (childNode.getType().equals(SlackConfig.NODE_FINISHED) ||
childNode.getType().equals(SlackConfig.NODE_ERRORABORTED) ||
childNode.getType().equals(SlackConfig.NODE_MANUALABORTED) ||
childNode.getType().equals(SlackConfig.NODE_MANUALPAUSED) ||
childNode.getType().equals(SlackConfig.NODE_SCHEDULEPAUSED) ||
childNode.getType().equals(SlackConfig.NODE_RESTARTED)) {
channel = "";
message = "";
for (int j = 0; j < childNode.getChildCount(); j++) {
SpecificationNode sn = childNode.getChild(j);
if (sn.getType().equals(SlackConfig.NODE_CHANNEL)) {
channel = sn.getAttributeValue(SlackConfig.ATTRIBUTE_VALUE);
} else if (sn.getType().equals(SlackConfig.NODE_MESSAGE)) {
message = sn.getAttributeValue(SlackConfig.ATTRIBUTE_VALUE);
}
}
addRecord(paramMap, childNode.getType(), channel, message);
}
}
}
}
protected static void addRecord(Map<String,Object> paramMap, String nodeType, String channel, String message) {
paramMap.put(nodeType+"_CHANNEL", channel);
paramMap.put(nodeType+"_MESSAGE", message);
}
/** Process a specification post.
* This method is called at the start of job's edit or view page, whenever there is a possibility that form
* data for a connection has been posted. Its purpose is to gather form information and modify the
* document specification accordingly. The name of the posted form is always "editjob".
* The connector will be connected before this method can be called.
*@param variableContext contains the post data, including binary file-upload information.
*@param locale is the locale the output is preferred to be in.
*@param ds is the current document specification for this job.
*@param connectionSequenceNumber is the unique number of this connection within the job.
*@return null if all is well, or a string error message if there is an error that should prevent saving of
* the job (and cause a redirection to an error page).
*/
@Override
public String processSpecificationPost(IPostParameters variableContext, Locale locale, Specification ds,
int connectionSequenceNumber)
throws ManifoldCFException {
return processMessageTab(variableContext, ds, connectionSequenceNumber);
}
protected String processMessageTab(IPostParameters variableContext, Specification ds,
int connectionSequenceNumber)
throws ManifoldCFException {
String seqPrefix = "s"+connectionSequenceNumber+"_";
// Remove legacy nodes always
removeNodes(ds, SlackConfig.NODE_CHANNEL);
removeNodes(ds, SlackConfig.NODE_MESSAGE);
// Gather all different kinds.
gatherRecord(ds, seqPrefix, variableContext, SlackConfig.NODE_FINISHED);
gatherRecord(ds, seqPrefix, variableContext, SlackConfig.NODE_ERRORABORTED);
gatherRecord(ds, seqPrefix, variableContext, SlackConfig.NODE_MANUALABORTED);
gatherRecord(ds, seqPrefix, variableContext, SlackConfig.NODE_MANUALPAUSED);
gatherRecord(ds, seqPrefix, variableContext, SlackConfig.NODE_SCHEDULEPAUSED);
gatherRecord(ds, seqPrefix, variableContext, SlackConfig.NODE_RESTARTED);
return null;
}
protected static void gatherRecord(Specification ds, String seqPrefix, IPostParameters variableContext, String nodeType) {
removeNodes(ds, nodeType);
SpecificationNode sn = new SpecificationNode(nodeType);
String channel = variableContext.getParameter(seqPrefix + nodeType + "_channel");
if (channel != null)
{
addNodeValue(sn, SlackConfig.NODE_CHANNEL, channel);
}
String message = variableContext.getParameter(seqPrefix + nodeType + "_message");
if (message != null)
{
addNodeValue(sn, SlackConfig.NODE_MESSAGE, message);
}
ds.addChild(ds.getChildCount(),sn);
}
/** View specification.
* This method is called in the body section of a job's view page. Its purpose is to present the document
* specification information to the user. The coder can presume that the HTML that is output from
* this configuration will be within appropriate <html> and <body> tags.
* The connector will be connected before this method can be called.
*@param out is the output to which any HTML should be sent.
*@param locale is the locale the output is preferred to be in.
*@param ds is the current document specification for this job.
*@param connectionSequenceNumber is the unique number of this connection within the job.
*/
@Override
public void viewSpecification(IHTTPOutput out, Locale locale, Specification ds,
int connectionSequenceNumber)
throws ManifoldCFException, IOException {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("SeqNum", Integer.toString(connectionSequenceNumber));
fillInMessageTab(paramMap, ds);
Messages.outputResourceWithVelocity(out, locale, "SpecificationView.html", paramMap);
}
///////////////////////////////////////End of specification UI///////////////////////////////////////////////
protected static void removeNodes(Specification ds, String nodeTypeName) {
int i = 0;
while (i < ds.getChildCount()) {
SpecificationNode sn = ds.getChild(i);
if (sn.getType().equals(nodeTypeName))
ds.removeChild(i);
else
i++;
}
}
protected static void addNodeValue(SpecificationNode ds, String nodeType, String value)
{
SpecificationNode sn = new SpecificationNode(nodeType);
sn.setAttribute(SlackConfig.ATTRIBUTE_VALUE,value);
ds.addChild(ds.getChildCount(),sn);
}
/** Handle Messaging exceptions in a consistent global manner */
protected static void handleIOException(IOException e, String context)
throws ManifoldCFException, ServiceInterruption
{
Logging.connectors.error("Slack: Error "+context+": "+e.getMessage(),e);
throw new ManifoldCFException("Error "+context+": "+e.getMessage(),e);
}
/** Class to set up connection.
*/
protected static class ConnectThread extends Thread
{
protected final String webHookUrl;
protected final SlackSession.ProxySettings proxySettings;
// Local session handle
protected SlackSession session = null;
protected Throwable exception = null;
public ConnectThread(String webHookUrl, SlackSession.ProxySettings proxySettings)
{
this.webHookUrl = webHookUrl;
this.proxySettings = proxySettings;
setDaemon(true);
}
public void run()
{
try
{
session = new SlackSession(webHookUrl, proxySettings);
}
catch (Throwable e)
{
exception = e;
}
}
public SlackSession finishUp()
throws IOException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof IOException)
throw (IOException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
return session;
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
/** Class to close the session.
*/
protected static class CloseSessionThread extends Thread
{
protected final SlackSession session;
protected Throwable exception = null;
public CloseSessionThread(SlackSession session)
{
this.session = session;
setDaemon(true);
}
public void run()
{
try
{
session.close();
}
catch (Throwable e)
{
exception = e;
}
}
public void finishUp()
throws IOException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof IOException)
throw (IOException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
/** Class to check the connection.
*/
protected static class CheckConnectionThread extends Thread
{
protected final SlackSession session;
protected Throwable exception = null;
public CheckConnectionThread(SlackSession session)
{
this.session = session;
setDaemon(true);
}
public void run()
{
try
{
session.checkConnection();
}
catch (Throwable e)
{
exception = e;
}
}
public void finishUp()
throws IOException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof IOException)
throw (IOException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
/** Class to send slack messages.
*/
protected static class SendThread extends Thread
{
protected final SlackSession session;
protected final String channel;
protected final String message;
protected Throwable exception = null;
public SendThread(SlackSession session, String channel, String message)
{
this.session = session;
this.channel = channel;
this.message = message;
setDaemon(true);
}
public void run()
{
try
{
session.send(channel, message);
}
catch (Throwable e)
{
exception = e;
}
}
public void finishUp()
throws IOException, InterruptedException
{
try
{
join();
if (exception != null)
{
if (exception instanceof RuntimeException)
throw (RuntimeException)exception;
else if (exception instanceof Error)
throw (Error)exception;
else if (exception instanceof IOException)
throw (IOException)exception;
else
throw new RuntimeException("Unknown exception type: "+exception.getClass().getName()+": "+exception.getMessage(),exception);
}
} catch (InterruptedException e) {
this.interrupt();
throw e;
}
}
}
}