This is an automated email from the ASF dual-hosted git repository.
oalsafi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push:
new 9777663 CAMEL-16255 Use of the slack-api-model to create rich content
(#5121)
9777663 is described below
commit 9777663454178f86b03ccad6b6f1d3134e767376
Author: Anthony Defraine <[email protected]>
AuthorDate: Wed Mar 3 16:35:31 2021 +0100
CAMEL-16255 Use of the slack-api-model to create rich content (#5121)
* CAMEL-16255 Use of the slack-api-model to create rich content
* Use slack-api-client to create rich content and keep backward
compatibility with the legacy webhookUrl
* Use constant for the conversations.list limit
Missing CustomSlackHttpClient for the SlackConsumer
* Remove since tag in documentation
---
.../apache/camel/catalog/docs/slack-component.adoc | 54 +++++-
components/camel-slack/pom.xml | 16 ++
.../component/slack/SlackComponentConfigurer.java | 3 +
.../component/slack/SlackEndpointConfigurer.java | 12 ++
.../component/slack/SlackEndpointUriFactory.java | 4 +-
.../org/apache/camel/component/slack/slack.json | 5 +-
.../camel-slack/src/main/docs/slack-component.adoc | 52 +++++-
.../component/slack/CustomSlackHttpClient.java | 46 +++++
.../camel/component/slack/SlackComponent.java | 14 ++
.../slack/SlackComponentVerifierExtension.java | 84 +++------
.../camel/component/slack/SlackConstants.java | 1 +
.../camel/component/slack/SlackConsumer.java | 179 +++++++-----------
.../camel/component/slack/SlackEndpoint.java | 35 +++-
.../camel/component/slack/SlackProducer.java | 203 ++++++++++-----------
.../camel/component/slack/utils/SlackUtils.java | 53 ------
.../camel/component/slack/SlackProducerTest.java | 27 ++-
.../dsl/SlackComponentBuilderFactory.java | 15 ++
.../endpoint/dsl/SlackEndpointBuilderFactory.java | 131 +++++++++++--
.../modules/ROOT/pages/slack-component.adoc | 54 +++++-
parent/pom.xml | 1 +
20 files changed, 615 insertions(+), 374 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/slack-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/slack-component.adoc
index f37ef4d..5363145 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/slack-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/slack-component.adoc
@@ -44,7 +44,7 @@ To send a direct message to a slackuser.
[source,java]
-------------------------
-slack:@username[?options]
+slack:@userID[?options]
-------------------------
== Options
@@ -52,7 +52,7 @@ slack:@username[?options]
// component options: START
-The Slack component supports 4 options, which are listed below.
+The Slack component supports 5 options, which are listed below.
@@ -62,6 +62,7 @@ The Slack component supports 4 options, which are listed
below.
| *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions occurred while the
consumer is trying to pickup incoming messages, or the likes, will now be
processed as a message and handled by the routing Error Handler. By default the
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with
exceptions, that will be logged at WARN or ERROR level and ignored. | false |
boolean
| *lazyStartProducer* (producer) | Whether the producer should be started lazy
(on the first message). By starting lazy you can use this to allow CamelContext
and routes to startup in situations where a producer may otherwise fail during
starting and cause the route to fail being started. By deferring this startup
to be lazy then the startup failure can be handled during routing messages via
Camel's routing error handlers. Beware that when the first message is processed
then creating and [...]
| *autowiredEnabled* (advanced) | Whether autowiring is enabled. This is used
for automatic autowiring options (the option must be marked as autowired) by
looking up in the registry to find if there is a single instance of matching
type, which then gets configured on the component. This can be used for
automatic configuring JDBC data sources, JMS connection factories, AWS Clients,
etc. | true | boolean
+| *token* (token) | The token to use | | String
| *webhookUrl* (webhook) | The incoming webhook URL | | String
|===
// component options: END
@@ -88,17 +89,19 @@ with the following path and query parameters:
|===
-=== Query Parameters (27 parameters):
+=== Query Parameters (29 parameters):
[width="100%",cols="2,5,^1,2",options="header"]
|===
| Name | Description | Default | Type
+| *token* (common) | The token to use | | String
| *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions occurred while the
consumer is trying to pickup incoming messages, or the likes, will now be
processed as a message and handled by the routing Error Handler. By default the
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with
exceptions, that will be logged at WARN or ERROR level and ignored. | false |
boolean
+| *conversationType* (consumer) | Type of conversation. There are 4 enums and
the value can be one of: PUBLIC_CHANNEL, PRIVATE_CHANNEL, MPIM, IM |
PUBLIC_CHANNEL | ConversationType
| *maxResults* (consumer) | The Max Result for the poll | 10 | String
+| *naturalOrder* (consumer) | Create exchanges in natural order (oldest to
newest) or not | false | boolean
| *sendEmptyMessageWhenIdle* (consumer) | If the polling consumer did not poll
any files, you can enable this option to send an empty message (no body)
instead. | false | boolean
| *serverUrl* (consumer) | The Server URL of the Slack instance |
https://slack.com | String
-| *token* (consumer) | The token to use | | String
| *exceptionHandler* (consumer) | To let the consumer use a custom
ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this
option is not in use. By default the consumer will deal with exceptions, that
will be logged at WARN or ERROR level and ignored. | | ExceptionHandler
| *exchangePattern* (consumer) | Sets the exchange pattern when the consumer
creates an exchange. There are 3 enums and the value can be one of: InOnly,
InOut, InOptionalOut | | ExchangePattern
| *pollStrategy* (consumer) | A pluggable
org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your
custom implementation to control error handling usually occurred during the
poll operation before an Exchange have been created and being routed in Camel.
| | PollingConsumerPollStrategy
@@ -129,13 +132,14 @@ with the following path and query parameters:
== SlackComponent
The SlackComponent with XML must be configured as a Spring or Blueprint
-bean that contains the incoming webhook url for the integration as a
+bean that contains the incoming webhook url or the app token for the
integration as a
parameter.
[source,xml]
-----------------------------------------------------------------------------------------------------------------------
<bean id="slack" class="org.apache.camel.component.slack.SlackComponent">
<property name="webhookUrl"
value="https://hooks.slack.com/services/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"/>
+ <property name="token"
value="xoxb-12345678901-1234567890123-xxxxxxxxxxxxxxxxxxxxxxxx"/>
</bean>
-----------------------------------------------------------------------------------------------------------------------
@@ -164,6 +168,35 @@ A CamelContext with Blueprint could be as:
</blueprint>
---------------------------------------------------------------------------------------------------------------------------
+== Producer
+
+*Since Camel 3.9.0* +
+ You can now use a token to send a message instead of WebhookUrl
+
+[source,java]
+---------------------------------------------------------------------------------------------------------------------------
+from("direct:test")
+ .to("slack:#random?token=RAW(<YOUR_TOKEN>)");
+---------------------------------------------------------------------------------------------------------------------------
+
+ You can now use the Slack API model to create blocks. You can read more about
it here https://api.slack.com/block-kit
+
+[source,java]
+---------------------------------------------------------------------------------------------------------------------------
+ public void testSlackAPIModelMessage() {
+ Message message = new Message();
+ message.setBlocks(Collections.singletonList(SectionBlock
+ .builder()
+ .text(MarkdownTextObject
+ .builder()
+ .text("*Hello from Camel!*")
+ .build())
+ .build()));
+
+ template.sendBody(test, message);
+ }
+---------------------------------------------------------------------------------------------------------------------------
+
== Consumer
You can use also a consumer for messages in channel
@@ -180,6 +213,15 @@ You'll need to create a Slack app and use it on your
workspace.
Use the 'Bot User OAuth Access Token' as token for the consumer endpoint.
-IMPORTANT: Add the `channels:history` and `channels:read` user token scope to
your app to grant it permission to view messages in the user's public channels.
+IMPORTANT: Add the corresponding history (`channels:history` or
`groups:history` or `mpim:history` or `im:history`) and
+read (`channels:read` or `groups:read` or `mpim:read` or `im:read`) user token
scope to your app to grant it permission to
+view messages in the corresponding channel. You will need to use the
conversationType option to set it up too (`PUBLIC_CHANNEL`, `PRIVATE_CHANNEL`,
`MPIM`, `IM`)
+
+*Since Camel 3.9.0* +
+ The naturalOrder option allows consuming messages from the oldest to the
newest.
+Originally you would get the newest first and consume backward (message 3 =>
message 2 => message 1)
+
+IMPORTANT: You can use the conversationType option to read history and
messages from a channel that is not only public
+(`PUBLIC_CHANNEL`,`PRIVATE_CHANNEL`, `MPIM`, `IM`)
include::camel-spring-boot::page$slack-starter.adoc[]
diff --git a/components/camel-slack/pom.xml b/components/camel-slack/pom.xml
index ff00ff7..7393360 100644
--- a/components/camel-slack/pom.xml
+++ b/components/camel-slack/pom.xml
@@ -47,6 +47,22 @@
</dependency>
<dependency>
+ <groupId>com.slack.api</groupId>
+ <artifactId>slack-api-client</artifactId>
+ <version>${slack-api-model-version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>${gson-version}</version>
+ </dependency>
+ <dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>${json-simple-version}</version>
diff --git
a/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackComponentConfigurer.java
b/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackComponentConfigurer.java
index 84b6429..3889b64 100644
---
a/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackComponentConfigurer.java
+++
b/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackComponentConfigurer.java
@@ -27,6 +27,7 @@ public class SlackComponentConfigurer extends
PropertyConfigurerSupport implemen
case "bridgeErrorHandler":
target.setBridgeErrorHandler(property(camelContext, boolean.class, value));
return true;
case "lazystartproducer":
case "lazyStartProducer":
target.setLazyStartProducer(property(camelContext, boolean.class, value));
return true;
+ case "token": target.setToken(property(camelContext,
java.lang.String.class, value)); return true;
case "webhookurl":
case "webhookUrl": target.setWebhookUrl(property(camelContext,
java.lang.String.class, value)); return true;
default: return false;
@@ -42,6 +43,7 @@ public class SlackComponentConfigurer extends
PropertyConfigurerSupport implemen
case "bridgeErrorHandler": return boolean.class;
case "lazystartproducer":
case "lazyStartProducer": return boolean.class;
+ case "token": return java.lang.String.class;
case "webhookurl":
case "webhookUrl": return java.lang.String.class;
default: return null;
@@ -58,6 +60,7 @@ public class SlackComponentConfigurer extends
PropertyConfigurerSupport implemen
case "bridgeErrorHandler": return target.isBridgeErrorHandler();
case "lazystartproducer":
case "lazyStartProducer": return target.isLazyStartProducer();
+ case "token": return target.getToken();
case "webhookurl":
case "webhookUrl": return target.getWebhookUrl();
default: return null;
diff --git
a/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackEndpointConfigurer.java
b/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackEndpointConfigurer.java
index e61ba22..64b78d4 100644
---
a/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackEndpointConfigurer.java
+++
b/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackEndpointConfigurer.java
@@ -29,6 +29,8 @@ public class SlackEndpointConfigurer extends
PropertyConfigurerSupport implement
case "backoffMultiplier":
target.setBackoffMultiplier(property(camelContext, int.class, value)); return
true;
case "bridgeerrorhandler":
case "bridgeErrorHandler":
target.setBridgeErrorHandler(property(camelContext, boolean.class, value));
return true;
+ case "conversationtype":
+ case "conversationType":
target.setConversationType(property(camelContext,
com.slack.api.model.ConversationType.class, value)); return true;
case "delay": target.setDelay(property(camelContext, long.class,
value)); return true;
case "exceptionhandler":
case "exceptionHandler":
target.setExceptionHandler(property(camelContext,
org.apache.camel.spi.ExceptionHandler.class, value)); return true;
@@ -45,6 +47,8 @@ public class SlackEndpointConfigurer extends
PropertyConfigurerSupport implement
case "lazyStartProducer":
target.setLazyStartProducer(property(camelContext, boolean.class, value));
return true;
case "maxresults":
case "maxResults": target.setMaxResults(property(camelContext,
java.lang.String.class, value)); return true;
+ case "naturalorder":
+ case "naturalOrder": target.setNaturalOrder(property(camelContext,
boolean.class, value)); return true;
case "pollstrategy":
case "pollStrategy": target.setPollStrategy(property(camelContext,
org.apache.camel.spi.PollingConsumerPollStrategy.class, value)); return true;
case "repeatcount":
@@ -85,6 +89,8 @@ public class SlackEndpointConfigurer extends
PropertyConfigurerSupport implement
case "backoffMultiplier": return int.class;
case "bridgeerrorhandler":
case "bridgeErrorHandler": return boolean.class;
+ case "conversationtype":
+ case "conversationType": return
com.slack.api.model.ConversationType.class;
case "delay": return long.class;
case "exceptionhandler":
case "exceptionHandler": return
org.apache.camel.spi.ExceptionHandler.class;
@@ -101,6 +107,8 @@ public class SlackEndpointConfigurer extends
PropertyConfigurerSupport implement
case "lazyStartProducer": return boolean.class;
case "maxresults":
case "maxResults": return java.lang.String.class;
+ case "naturalorder":
+ case "naturalOrder": return boolean.class;
case "pollstrategy":
case "pollStrategy": return
org.apache.camel.spi.PollingConsumerPollStrategy.class;
case "repeatcount":
@@ -142,6 +150,8 @@ public class SlackEndpointConfigurer extends
PropertyConfigurerSupport implement
case "backoffMultiplier": return target.getBackoffMultiplier();
case "bridgeerrorhandler":
case "bridgeErrorHandler": return target.isBridgeErrorHandler();
+ case "conversationtype":
+ case "conversationType": return target.getConversationType();
case "delay": return target.getDelay();
case "exceptionhandler":
case "exceptionHandler": return target.getExceptionHandler();
@@ -158,6 +168,8 @@ public class SlackEndpointConfigurer extends
PropertyConfigurerSupport implement
case "lazyStartProducer": return target.isLazyStartProducer();
case "maxresults":
case "maxResults": return target.getMaxResults();
+ case "naturalorder":
+ case "naturalOrder": return target.isNaturalOrder();
case "pollstrategy":
case "pollStrategy": return target.getPollStrategy();
case "repeatcount":
diff --git
a/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackEndpointUriFactory.java
b/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackEndpointUriFactory.java
index 207be9e..88b2c77 100644
---
a/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackEndpointUriFactory.java
+++
b/components/camel-slack/src/generated/java/org/apache/camel/component/slack/SlackEndpointUriFactory.java
@@ -20,8 +20,9 @@ public class SlackEndpointUriFactory extends
org.apache.camel.support.component.
private static final Set<String> PROPERTY_NAMES;
private static final Set<String> SECRET_PROPERTY_NAMES;
static {
- Set<String> props = new HashSet<>(28);
+ Set<String> props = new HashSet<>(30);
props.add("backoffMultiplier");
+ props.add("naturalOrder");
props.add("channel");
props.add("initialDelay");
props.add("scheduler");
@@ -43,6 +44,7 @@ public class SlackEndpointUriFactory extends
org.apache.camel.support.component.
props.add("webhookUrl");
props.add("backoffIdleThreshold");
props.add("token");
+ props.add("conversationType");
props.add("lazyStartProducer");
props.add("delay");
props.add("pollStrategy");
diff --git
a/components/camel-slack/src/generated/resources/org/apache/camel/component/slack/slack.json
b/components/camel-slack/src/generated/resources/org/apache/camel/component/slack/slack.json
index 7c4befa..c288126 100644
---
a/components/camel-slack/src/generated/resources/org/apache/camel/component/slack/slack.json
+++
b/components/camel-slack/src/generated/resources/org/apache/camel/component/slack/slack.json
@@ -25,15 +25,18 @@
"bridgeErrorHandler": { "kind": "property", "displayName": "Bridge Error
Handler", "group": "consumer", "label": "consumer", "required": false, "type":
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": false, "description": "Allows for bridging the
consumer to the Camel routing Error Handler, which mean any exceptions occurred
while the consumer is trying to pickup incoming messages, or the likes, will
now be processed as a me [...]
"lazyStartProducer": { "kind": "property", "displayName": "Lazy Start
Producer", "group": "producer", "label": "producer", "required": false, "type":
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": false, "description": "Whether the producer
should be started lazy (on the first message). By starting lazy you can use
this to allow CamelContext and routes to startup in situations where a producer
may otherwise fail during star [...]
"autowiredEnabled": { "kind": "property", "displayName": "Autowired
Enabled", "group": "advanced", "label": "advanced", "required": false, "type":
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": true, "description": "Whether autowiring is
enabled. This is used for automatic autowiring options (the option must be
marked as autowired) by looking up in the registry to find if there is a single
instance of matching type, which t [...]
+ "token": { "kind": "property", "displayName": "Token", "group": "token",
"label": "token", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "The token to use" },
"webhookUrl": { "kind": "property", "displayName": "Webhook Url", "group":
"webhook", "label": "webhook", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "The incoming webhook URL" }
},
"properties": {
"channel": { "kind": "path", "displayName": "Channel", "group": "common",
"label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The channel name (syntax #name) or
slackuser (syntax userName) to send a message directly to an user." },
+ "token": { "kind": "parameter", "displayName": "Token", "group": "common",
"label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": true,
"description": "The token to use" },
"bridgeErrorHandler": { "kind": "parameter", "displayName": "Bridge Error
Handler", "group": "consumer", "label": "consumer", "required": false, "type":
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": false, "description": "Allows for bridging the
consumer to the Camel routing Error Handler, which mean any exceptions occurred
while the consumer is trying to pickup incoming messages, or the likes, will
now be processed as a m [...]
+ "conversationType": { "kind": "parameter", "displayName": "Conversation
Type", "group": "consumer", "label": "consumer", "required": false, "type":
"object", "javaType": "com.slack.api.model.ConversationType", "enum": [
"PUBLIC_CHANNEL", "PRIVATE_CHANNEL", "MPIM", "IM" ], "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "PUBLIC_CHANNEL",
"description": "Type of conversation" },
"maxResults": { "kind": "parameter", "displayName": "Max Results",
"group": "consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "10", "description": "The Max Result for the
poll" },
+ "naturalOrder": { "kind": "parameter", "displayName": "Natural Order",
"group": "consumer", "label": "consumer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "description": "Create exchanges in natural order
(oldest to newest) or not" },
"sendEmptyMessageWhenIdle": { "kind": "parameter", "displayName": "Send
Empty Message When Idle", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false, "description": "If
the polling consumer did not poll any files, you can enable this option to send
an empty message (no body) instead." },
"serverUrl": { "kind": "parameter", "displayName": "Server Url", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": "https:\/\/slack.com", "description": "The
Server URL of the Slack instance" },
- "token": { "kind": "parameter", "displayName": "Token", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "description": "The token to use" },
"exceptionHandler": { "kind": "parameter", "displayName": "Exception
Handler", "group": "consumer (advanced)", "label": "consumer,advanced",
"required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By default the
con [...]
"exchangePattern": { "kind": "parameter", "displayName": "Exchange
Pattern", "group": "consumer (advanced)", "label": "consumer,advanced",
"required": false, "type": "object", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut",
"InOptionalOut" ], "deprecated": false, "autowired": false, "secret": false,
"description": "Sets the exchange pattern when the consumer creates an
exchange." },
"pollStrategy": { "kind": "parameter", "displayName": "Poll Strategy",
"group": "consumer (advanced)", "label": "consumer,advanced", "required":
false, "type": "object", "javaType":
"org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false,
"autowired": false, "secret": false, "description": "A pluggable
org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your
custom implementation to control error handling usually occurred during the
poll operation [...]
diff --git a/components/camel-slack/src/main/docs/slack-component.adoc
b/components/camel-slack/src/main/docs/slack-component.adoc
index f37ef4d..be1060a 100644
--- a/components/camel-slack/src/main/docs/slack-component.adoc
+++ b/components/camel-slack/src/main/docs/slack-component.adoc
@@ -44,7 +44,7 @@ To send a direct message to a slackuser.
[source,java]
-------------------------
-slack:@username[?options]
+slack:@userID[?options]
-------------------------
== Options
@@ -52,7 +52,7 @@ slack:@username[?options]
// component options: START
-The Slack component supports 4 options, which are listed below.
+The Slack component supports 5 options, which are listed below.
@@ -62,6 +62,7 @@ The Slack component supports 4 options, which are listed
below.
| *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions occurred while the
consumer is trying to pickup incoming messages, or the likes, will now be
processed as a message and handled by the routing Error Handler. By default the
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with
exceptions, that will be logged at WARN or ERROR level and ignored. | false |
boolean
| *lazyStartProducer* (producer) | Whether the producer should be started lazy
(on the first message). By starting lazy you can use this to allow CamelContext
and routes to startup in situations where a producer may otherwise fail during
starting and cause the route to fail being started. By deferring this startup
to be lazy then the startup failure can be handled during routing messages via
Camel's routing error handlers. Beware that when the first message is processed
then creating and [...]
| *autowiredEnabled* (advanced) | Whether autowiring is enabled. This is used
for automatic autowiring options (the option must be marked as autowired) by
looking up in the registry to find if there is a single instance of matching
type, which then gets configured on the component. This can be used for
automatic configuring JDBC data sources, JMS connection factories, AWS Clients,
etc. | true | boolean
+| *token* (token) | The token to use | | String
| *webhookUrl* (webhook) | The incoming webhook URL | | String
|===
// component options: END
@@ -88,17 +89,19 @@ with the following path and query parameters:
|===
-=== Query Parameters (27 parameters):
+=== Query Parameters (29 parameters):
[width="100%",cols="2,5,^1,2",options="header"]
|===
| Name | Description | Default | Type
+| *token* (common) | The token to use | | String
| *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions occurred while the
consumer is trying to pickup incoming messages, or the likes, will now be
processed as a message and handled by the routing Error Handler. By default the
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with
exceptions, that will be logged at WARN or ERROR level and ignored. | false |
boolean
+| *conversationType* (consumer) | Type of conversation. There are 4 enums and
the value can be one of: PUBLIC_CHANNEL, PRIVATE_CHANNEL, MPIM, IM |
PUBLIC_CHANNEL | ConversationType
| *maxResults* (consumer) | The Max Result for the poll | 10 | String
+| *naturalOrder* (consumer) | Create exchanges in natural order (oldest to
newest) or not | false | boolean
| *sendEmptyMessageWhenIdle* (consumer) | If the polling consumer did not poll
any files, you can enable this option to send an empty message (no body)
instead. | false | boolean
| *serverUrl* (consumer) | The Server URL of the Slack instance |
https://slack.com | String
-| *token* (consumer) | The token to use | | String
| *exceptionHandler* (consumer) | To let the consumer use a custom
ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this
option is not in use. By default the consumer will deal with exceptions, that
will be logged at WARN or ERROR level and ignored. | | ExceptionHandler
| *exchangePattern* (consumer) | Sets the exchange pattern when the consumer
creates an exchange. There are 3 enums and the value can be one of: InOnly,
InOut, InOptionalOut | | ExchangePattern
| *pollStrategy* (consumer) | A pluggable
org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your
custom implementation to control error handling usually occurred during the
poll operation before an Exchange have been created and being routed in Camel.
| | PollingConsumerPollStrategy
@@ -129,13 +132,14 @@ with the following path and query parameters:
== SlackComponent
The SlackComponent with XML must be configured as a Spring or Blueprint
-bean that contains the incoming webhook url for the integration as a
+bean that contains the incoming webhook url or the app token for the
integration as a
parameter.
[source,xml]
-----------------------------------------------------------------------------------------------------------------------
<bean id="slack" class="org.apache.camel.component.slack.SlackComponent">
<property name="webhookUrl"
value="https://hooks.slack.com/services/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"/>
+ <property name="token"
value="xoxb-12345678901-1234567890123-xxxxxxxxxxxxxxxxxxxxxxxx"/>
</bean>
-----------------------------------------------------------------------------------------------------------------------
@@ -164,6 +168,34 @@ A CamelContext with Blueprint could be as:
</blueprint>
---------------------------------------------------------------------------------------------------------------------------
+== Producer
+
+You can now use a token to send a message instead of WebhookUrl
+
+[source,java]
+---------------------------------------------------------------------------------------------------------------------------
+from("direct:test")
+ .to("slack:#random?token=RAW(<YOUR_TOKEN>)");
+---------------------------------------------------------------------------------------------------------------------------
+
+You can now use the Slack API model to create blocks. You can read more about
it here https://api.slack.com/block-kit
+
+[source,java]
+---------------------------------------------------------------------------------------------------------------------------
+ public void testSlackAPIModelMessage() {
+ Message message = new Message();
+ message.setBlocks(Collections.singletonList(SectionBlock
+ .builder()
+ .text(MarkdownTextObject
+ .builder()
+ .text("*Hello from Camel!*")
+ .build())
+ .build()));
+
+ template.sendBody(test, message);
+ }
+---------------------------------------------------------------------------------------------------------------------------
+
== Consumer
You can use also a consumer for messages in channel
@@ -180,6 +212,14 @@ You'll need to create a Slack app and use it on your
workspace.
Use the 'Bot User OAuth Access Token' as token for the consumer endpoint.
-IMPORTANT: Add the `channels:history` and `channels:read` user token scope to
your app to grant it permission to view messages in the user's public channels.
+IMPORTANT: Add the corresponding history (`channels:history` or
`groups:history` or `mpim:history` or `im:history`) and
+read (`channels:read` or `groups:read` or `mpim:read` or `im:read`) user token
scope to your app to grant it permission to
+view messages in the corresponding channel. You will need to use the
conversationType option to set it up too (`PUBLIC_CHANNEL`, `PRIVATE_CHANNEL`,
`MPIM`, `IM`)
+
+The naturalOrder option allows consuming messages from the oldest to the
newest.
+Originally you would get the newest first and consume backward (message 3 =>
message 2 => message 1)
+
+IMPORTANT: You can use the conversationType option to read history and
messages from a channel that is not only public
+(`PUBLIC_CHANNEL`,`PRIVATE_CHANNEL`, `MPIM`, `IM`)
include::camel-spring-boot::page$slack-starter.adoc[]
diff --git
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/CustomSlackHttpClient.java
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/CustomSlackHttpClient.java
new file mode 100644
index 0000000..10c6718
--- /dev/null
+++
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/CustomSlackHttpClient.java
@@ -0,0 +1,46 @@
+/*
+ * 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.camel.component.slack;
+
+import java.io.IOException;
+
+import com.slack.api.SlackConfig;
+import com.slack.api.util.http.SlackHttpClient;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+/**
+ * Slack-api-client use the OkHttpClient v4.x.x We need to override the
SlackHttpClient to force the function
+ * postJsonBody to work with the v3.x.x
+ */
+public class CustomSlackHttpClient extends SlackHttpClient {
+
+ private static final MediaType MEDIA_TYPE_APPLICATION_JSON =
MediaType.parse("application/json; charset=utf-8");
+
+ private final SlackConfig config = SlackConfig.DEFAULT;
+ private final OkHttpClient okHttpClient = buildOkHttpClient(config);
+
+ @Override
+ public Response postJsonBody(String url, Object obj) throws IOException {
+ RequestBody body = RequestBody.create((String) obj,
MEDIA_TYPE_APPLICATION_JSON);
+ Request request = new Request.Builder().url(url).post(body).build();
+ return okHttpClient.newCall(request).execute();
+ }
+}
diff --git
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponent.java
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponent.java
index 4264e02..eac4ef4 100644
---
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponent.java
+++
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponent.java
@@ -30,6 +30,9 @@ public class SlackComponent extends DefaultComponent {
@Metadata(label = "webhook")
private String webhookUrl;
+ @Metadata(label = "token")
+ private String token;
+
public SlackComponent() {
this(null);
}
@@ -56,4 +59,15 @@ public class SlackComponent extends DefaultComponent {
public void setWebhookUrl(String webhookUrl) {
this.webhookUrl = webhookUrl;
}
+
+ public String getToken() {
+ return token;
+ }
+
+ /**
+ * The token to use
+ */
+ public void setToken(String token) {
+ this.token = token;
+ }
}
diff --git
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponentVerifierExtension.java
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponentVerifierExtension.java
index 338e803..6d612fb 100644
---
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponentVerifierExtension.java
+++
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackComponentVerifierExtension.java
@@ -16,30 +16,24 @@
*/
package org.apache.camel.component.slack;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
+import java.util.Collections;
import java.util.Map;
+import com.google.gson.Gson;
+import com.slack.api.Slack;
+import com.slack.api.methods.response.conversations.ConversationsListResponse;
+import com.slack.api.model.ConversationType;
+import com.slack.api.webhook.WebhookResponse;
import
org.apache.camel.component.extension.verifier.DefaultComponentVerifierExtension;
import org.apache.camel.component.extension.verifier.ResultBuilder;
import org.apache.camel.component.extension.verifier.ResultErrorBuilder;
import org.apache.camel.component.slack.helper.SlackMessage;
import org.apache.camel.util.ObjectHelper;
-import org.apache.camel.util.json.JsonObject;
-import org.apache.camel.util.json.Jsoner;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.entity.UrlEncodedFormEntity;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.message.BasicNameValuePair;
-
-import static org.apache.camel.component.slack.utils.SlackUtils.readResponse;
public class SlackComponentVerifierExtension extends
DefaultComponentVerifierExtension {
+ private static final Gson GSON = new Gson();
+
public SlackComponentVerifierExtension() {
this("slack");
}
@@ -57,12 +51,12 @@ public class SlackComponentVerifierExtension extends
DefaultComponentVerifierExt
if (ObjectHelper.isEmpty(parameters.get("token")) &&
ObjectHelper.isEmpty(parameters.get("webhookUrl"))) {
builder.error(ResultErrorBuilder.withCodeAndDescription(VerificationError.StandardCode.GENERIC,
- "You must specify a webhookUrl (for producer) or a token
(for consumer)").parameterKey("webhookUrl")
- .parameterKey("token").build());
+ "You must specify a webhookUrl (for producer) or a token
(for producer and consumer)")
+ .parameterKey("webhookUrl").parameterKey("token").build());
}
if (ObjectHelper.isNotEmpty(parameters.get("token")) &&
ObjectHelper.isNotEmpty(parameters.get("webhookUrl"))) {
builder.error(ResultErrorBuilder.withCodeAndDescription(VerificationError.StandardCode.GENERIC,
- "You must specify a webhookUrl (for producer) or a token
(for consumer). You can't specify both.")
+ "You must specify a webhookUrl (for producer) or a token
(for producer and consumer). You can't specify both.")
.parameterKey("webhookUrl").parameterKey("token").build());
}
return builder.build();
@@ -78,35 +72,24 @@ public class SlackComponentVerifierExtension extends
DefaultComponentVerifierExt
}
private void verifyCredentials(ResultBuilder builder, Map<String, Object>
parameters) {
-
String webhookUrl = (String) parameters.get("webhookUrl");
if (ObjectHelper.isNotEmpty(webhookUrl)) {
try {
- HttpClient client =
HttpClientBuilder.create().useSystemProperties().build();
- HttpPost httpPost = new HttpPost(webhookUrl);
-
// Build Helper object
SlackMessage slackMessage;
slackMessage = new SlackMessage();
slackMessage.setText("Test connection");
- // Set the post body
- String json = asJson(slackMessage);
- StringEntity body = new StringEntity(json);
-
- // Do the post
- httpPost.setEntity(body);
-
- HttpResponse response = client.execute(httpPost);
+ WebhookResponse response
+ = Slack.getInstance(new
CustomSlackHttpClient()).send(webhookUrl, GSON.toJson(slackMessage));
// 2xx is OK, anything else we regard as failure
- if (response.getStatusLine().getStatusCode() < 200 ||
response.getStatusLine().getStatusCode() > 299) {
- builder
- .error(ResultErrorBuilder
-
.withCodeAndDescription(VerificationError.StandardCode.AUTHENTICATION, "Invalid
webhookUrl")
- .parameterKey("webhookUrl").build());
+ if (response.getCode() < 200 || response.getCode() > 299) {
+ builder.error(ResultErrorBuilder
+
.withCodeAndDescription(VerificationError.StandardCode.AUTHENTICATION, "Invalid
webhookUrl")
+ .parameterKey("webhookUrl").build());
}
} catch (Exception e) {
builder.error(ResultErrorBuilder
@@ -118,23 +101,12 @@ public class SlackComponentVerifierExtension extends
DefaultComponentVerifierExt
String token = (String) parameters.get("token");
try {
- HttpClient client =
HttpClientBuilder.create().useSystemProperties().build();
- HttpPost httpPost = new HttpPost(parameters.get("serverUrl") +
"/api/conversations.list");
-
- List<BasicNameValuePair> params = new ArrayList<>();
- params.add(new BasicNameValuePair("token", token));
- httpPost.setEntity(new UrlEncodedFormEntity(params));
+ ConversationsListResponse response = Slack.getInstance(new
CustomSlackHttpClient()).methods(token)
+ .conversationsList(req -> req
+
.types(Collections.singletonList(ConversationType.PUBLIC_CHANNEL))
+ .limit(1));
- HttpResponse response = client.execute(httpPost);
-
- String jsonString =
readResponse(response.getEntity().getContent());
- if (response.getStatusLine().getStatusCode() < 200 ||
response.getStatusLine().getStatusCode() > 299) {
- builder.error(ResultErrorBuilder
-
.withCodeAndDescription(VerificationError.StandardCode.AUTHENTICATION, "Invalid
token")
- .parameterKey("token").build());
- }
- JsonObject obj = (JsonObject) Jsoner.deserialize(jsonString);
- if (obj.get("ok") != null && obj.get("ok").equals(false)) {
+ if (!response.isOk()) {
builder.error(ResultErrorBuilder
.withCodeAndDescription(VerificationError.StandardCode.AUTHENTICATION, "Invalid
token")
.parameterKey("token").build());
@@ -144,18 +116,6 @@ public class SlackComponentVerifierExtension extends
DefaultComponentVerifierExt
.withCodeAndDescription(VerificationError.StandardCode.AUTHENTICATION, "Invalid
token")
.parameterKey("token").build());
}
-
}
}
-
- protected String asJson(SlackMessage message) {
- Map<String, Object> jsonMap = new HashMap<>();
-
- // Put the values in a map
- jsonMap.put(SlackConstants.SLACK_TEXT_FIELD, message.getText());
-
- // Generate a JSONObject
- return new JsonObject(jsonMap).toJson();
- }
-
}
diff --git
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConstants.java
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConstants.java
index 2609b3e..e1755a9 100644
---
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConstants.java
+++
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConstants.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.slack;
+@Deprecated
public final class SlackConstants {
public static final String SLACK_USERNAME_FIELD = "username";
diff --git
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConsumer.java
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConsumer.java
index fde7f10..4296483 100644
---
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConsumer.java
+++
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackConsumer.java
@@ -17,102 +17,91 @@
package org.apache.camel.component.slack;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
+import com.slack.api.Slack;
+import com.slack.api.methods.SlackApiException;
+import
com.slack.api.methods.response.conversations.ConversationsHistoryResponse;
+import com.slack.api.methods.response.conversations.ConversationsListResponse;
+import com.slack.api.model.Conversation;
+import com.slack.api.model.Message;
import org.apache.camel.Exchange;
-import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.RuntimeCamelException;
-import org.apache.camel.component.slack.helper.SlackMessage;
import org.apache.camel.support.ScheduledBatchPollingConsumer;
import org.apache.camel.util.CastUtils;
import org.apache.camel.util.ObjectHelper;
-import org.apache.camel.util.json.DeserializationException;
-import org.apache.camel.util.json.JsonArray;
-import org.apache.camel.util.json.JsonObject;
-import org.apache.camel.util.json.Jsoner;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.entity.UrlEncodedFormEntity;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.message.BasicNameValuePair;
-
-import static org.apache.camel.component.slack.utils.SlackUtils.readResponse;
public class SlackConsumer extends ScheduledBatchPollingConsumer {
- private SlackEndpoint slackEndpoint;
+ private static final int CONVERSATIONS_LIST_LIMIT = 200;
+ private final SlackEndpoint slackEndpoint;
+ private Slack slack;
private String timestamp;
private String channelId;
- private CloseableHttpClient client;
- public SlackConsumer(SlackEndpoint endpoint, Processor processor) throws
IOException, DeserializationException {
+ public SlackConsumer(SlackEndpoint endpoint, Processor processor) {
super(endpoint, processor);
this.slackEndpoint = endpoint;
}
@Override
protected void doStart() throws Exception {
- this.client = HttpClientBuilder.create().useSystemProperties().build();
+ this.slack = Slack.getInstance(new CustomSlackHttpClient());
+ this.channelId = getChannelId(slackEndpoint.getChannel(), null);
super.doStart();
- this.channelId = getChannelId(slackEndpoint.getChannel());
}
@Override
protected void doStop() throws Exception {
super.doStop();
- if (client != null) {
- client.close();
+ if (slack != null) {
+ slack.close();
}
}
@Override
protected int poll() throws Exception {
- Queue<Exchange> exchanges;
-
- HttpPost httpPost = new HttpPost(slackEndpoint.getServerUrl() +
"/api/conversations.history");
- List<BasicNameValuePair> params = new ArrayList<>();
- params.add(new BasicNameValuePair(SlackConstants.SLACK_CHANNEL_FIELD,
channelId));
- if (ObjectHelper.isNotEmpty(timestamp)) {
- params.add(new BasicNameValuePair("oldest", timestamp));
+ // Maximum limit is 1000. Slack recommends no more than 200 results at
a time.
+ // https://api.slack.com/methods/conversations.history
+ // We set the limit to 1 the first call to set the timestamp of the
last message of the history
+ ConversationsHistoryResponse response =
slack.methods(slackEndpoint.getToken()).conversationsHistory(req -> req
+ .channel(channelId)
+ .oldest(timestamp)
+ .limit(timestamp != null ?
Integer.parseInt(slackEndpoint.getMaxResults()) : 1));
+
+ if (!response.isOk()) {
+ throw new RuntimeCamelException("API request conversations.history
to Slack failed: " + response);
}
- params.add(new BasicNameValuePair("count",
slackEndpoint.getMaxResults()));
- params.add(new BasicNameValuePair("token", slackEndpoint.getToken()));
- httpPost.setEntity(new UrlEncodedFormEntity(params));
-
- HttpResponse response = client.execute(httpPost);
-
- String jsonString = readResponse(response);
-
- JsonObject c = (JsonObject) Jsoner.deserialize(jsonString);
- checkSlackReply(c);
-
- JsonArray list = c.getCollection("messages");
- exchanges = createExchanges(list);
+ Queue<Exchange> exchanges = createExchanges(response.getMessages());
return processBatch(CastUtils.cast(exchanges));
}
- private Queue<Exchange> createExchanges(List<Object> list) {
+ private Queue<Exchange> createExchanges(final List<Message> list) {
Queue<Exchange> answer = new LinkedList<>();
if (ObjectHelper.isNotEmpty(list)) {
- Iterator it = list.iterator();
- int i = 0;
- while (it.hasNext()) {
- Object object = it.next();
- JsonObject singleMess = (JsonObject) object;
- if (i == 0) {
- timestamp = (String) singleMess.get("ts");
+ if (slackEndpoint.isNaturalOrder()) {
+ for (int i = list.size() - 1; i >= 0; i--) {
+ Message message = list.get(i);
+ if (i == 0) {
+ timestamp = message.getTs();
+ }
+ Exchange exchange = createExchange(message);
+ answer.add(exchange);
+ }
+ } else {
+ for (int i = 0; i < list.size(); i++) {
+ Message message = list.get(i);
+ if (i == 0) {
+ timestamp = message.getTs();
+ }
+ Exchange exchange = createExchange(message);
+ answer.add(exchange);
}
- i++;
- Exchange exchange = createExchange(singleMess);
- answer.add(exchange);
}
}
return answer;
@@ -141,68 +130,36 @@ public class SlackConsumer extends
ScheduledBatchPollingConsumer {
return total;
}
- private String getChannelId(String channel) throws IOException,
DeserializationException {
- HttpPost httpPost = new HttpPost(slackEndpoint.getServerUrl() +
"/api/conversations.list");
-
- List<BasicNameValuePair> params = new ArrayList<>();
- params.add(new BasicNameValuePair("token", slackEndpoint.getToken()));
- httpPost.setEntity(new UrlEncodedFormEntity(params));
-
- HttpResponse response = client.execute(httpPost);
-
- String jsonString = readResponse(response);
- JsonObject c = (JsonObject) Jsoner.deserialize(jsonString);
-
- checkSlackReply(c);
-
- Collection<JsonObject> channels = c.getCollection("channels");
- if (channels == null) {
- throw new RuntimeCamelException("The response was successful but
no channel list was provided");
- }
-
- for (JsonObject singleChannel : channels) {
- if (singleChannel.get("name") != null) {
- if (singleChannel.get("name").equals(channel)) {
- if (singleChannel.get("id") != null) {
- return (String) singleChannel.get("id");
- }
- }
+ private String getChannelId(final String channel, final String cursor) {
+ try {
+ // Maximum limit is 1000. Slack recommends no more than 200
results at a time.
+ // https://api.slack.com/methods/conversations.list
+ ConversationsListResponse response =
slack.methods(slackEndpoint.getToken()).conversationsList(req -> req
+
.types(Collections.singletonList(slackEndpoint.getConversationType()))
+ .cursor(cursor)
+ .limit(CONVERSATIONS_LIST_LIMIT));
+
+ if (!response.isOk()) {
+ throw new RuntimeCamelException("API request
conversations.list to Slack failed: " + response);
}
- }
-
- return jsonString;
- }
-
- private void checkSlackReply(JsonObject c) {
- boolean okStatus = c.getBoolean("ok");
- if (!okStatus) {
- String errorMessage = c.getString("error");
-
- if (errorMessage == null || errorMessage.isEmpty()) {
- errorMessage = "the slack server did not provide error
details";
- }
-
- throw new RuntimeCamelException(String.format("API request to
Slack failed: %s", errorMessage));
+ return response.getChannels().stream()
+ .filter(it -> it.getName().equals(channel))
+ .map(Conversation::getId)
+ .findFirst().orElseGet(() -> {
+ if
(ObjectHelper.isNotEmpty(response.getResponseMetadata().getNextCursor())) {
+ throw new
RuntimeCamelException(String.format("Channel %s not found", channel));
+ }
+ return getChannelId(channel,
response.getResponseMetadata().getNextCursor());
+ });
+ } catch (IOException | SlackApiException e) {
+ throw new RuntimeCamelException("API request conversations.list to
Slack failed", e);
}
}
- public Exchange createExchange(JsonObject object) {
+ private Exchange createExchange(Message object) {
Exchange exchange = createExchange(true);
- SlackMessage slackMessage = new SlackMessage();
- String text = object.getString(SlackConstants.SLACK_TEXT_FIELD);
- String user = object.getString("user");
- slackMessage.setText(text);
- slackMessage.setUser(user);
- if (ObjectHelper.isNotEmpty(object.get("icons"))) {
- JsonObject icons = object.getMap("icons");
- if (ObjectHelper.isNotEmpty(icons.get("emoji"))) {
- slackMessage.setIconEmoji(icons.getString("emoji"));
- }
- }
- Message message = exchange.getIn();
- message.setBody(slackMessage);
+ exchange.getIn().setBody(object);
return exchange;
}
-
}
diff --git
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java
index 9893b21..814d03d 100644
---
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java
+++
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackEndpoint.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.slack;
+import com.slack.api.model.ConversationType;
import org.apache.camel.Category;
import org.apache.camel.Consumer;
import org.apache.camel.Processor;
@@ -49,12 +50,18 @@ public class SlackEndpoint extends ScheduledPollEndpoint {
@UriParam(label = "producer")
@Deprecated
private String iconEmoji;
- @UriParam(label = "consumer", secret = true)
+ @UriParam(secret = true)
private String token;
@UriParam(label = "consumer", defaultValue = "10")
private String maxResults = "10";
@UriParam(label = "consumer", defaultValue = "https://slack.com")
private String serverUrl = "https://slack.com";
+ @UriParam(label = "consumer", defaultValue = "false", javaType = "boolean",
+ description = "Create exchanges in natural order (oldest to
newest) or not")
+ private boolean naturalOrder;
+ @UriParam(label = "consumer", enums =
"PUBLIC_CHANNEL,PRIVATE_CHANNEL,MPIM,IM", defaultValue = "PUBLIC_CHANNEL",
+ description = "Type of conversation")
+ private ConversationType conversationType =
ConversationType.PUBLIC_CHANNEL;
/**
* Constructor for SlackEndpoint
@@ -66,11 +73,16 @@ public class SlackEndpoint extends ScheduledPollEndpoint {
public SlackEndpoint(String uri, String channelName, SlackComponent
component) {
super(uri, component);
this.webhookUrl = component.getWebhookUrl();
+ this.token = component.getToken();
this.channel = channelName;
}
@Override
public Producer createProducer() throws Exception {
+ if (ObjectHelper.isEmpty(token) && ObjectHelper.isEmpty(webhookUrl)) {
+ throw new RuntimeCamelException(
+ "Missing required endpoint configuration: token or
webhookUrl must be defined for Slack producer");
+ }
SlackProducer producer = new SlackProducer(this);
return producer;
}
@@ -178,4 +190,25 @@ public class SlackEndpoint extends ScheduledPollEndpoint {
this.serverUrl = serverUrl;
}
+ /**
+ * Is consuming message in natural order
+ */
+ public void setNaturalOrder(boolean naturalOrder) {
+ this.naturalOrder = naturalOrder;
+ }
+
+ public boolean isNaturalOrder() {
+ return naturalOrder;
+ }
+
+ /**
+ * The type of the conversation
+ */
+ public void setConversationType(ConversationType conversationType) {
+ this.conversationType = conversationType;
+ }
+
+ public ConversationType getConversationType() {
+ return conversationType;
+ }
}
diff --git
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackProducer.java
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackProducer.java
index d1321a8..20b8d7a 100644
---
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackProducer.java
+++
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/SlackProducer.java
@@ -16,28 +16,26 @@
*/
package org.apache.camel.component.slack;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
+import java.io.IOException;
+
+import com.google.gson.Gson;
+import com.slack.api.Slack;
+import com.slack.api.methods.SlackApiException;
+import com.slack.api.methods.response.chat.ChatPostMessageResponse;
+import com.slack.api.model.Message;
+import com.slack.api.webhook.WebhookResponse;
import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelExchangeException;
import org.apache.camel.Exchange;
import org.apache.camel.component.slack.helper.SlackMessage;
import org.apache.camel.support.DefaultAsyncProducer;
-import org.apache.camel.support.ExchangeHelper;
-import org.apache.camel.util.json.JsonObject;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.util.EntityUtils;
public class SlackProducer extends DefaultAsyncProducer {
+ private static final Gson GSON = new Gson();
+
private final SlackEndpoint slackEndpoint;
- private CloseableHttpClient client;
+ private Slack slack;
public SlackProducer(SlackEndpoint endpoint) {
super(endpoint);
@@ -46,133 +44,116 @@ public class SlackProducer extends DefaultAsyncProducer {
@Override
protected void doStart() throws Exception {
- this.client = HttpClientBuilder.create().useSystemProperties().build();
+ this.slack = Slack.getInstance(new CustomSlackHttpClient());
super.doStart();
}
@Override
protected void doStop() throws Exception {
super.doStop();
-
- if (client != null) {
- client.close();
- client = null;
+ if (slack != null) {
+ slack.close();
}
}
@Override
public boolean process(Exchange exchange, AsyncCallback callback) {
-
- // Create Post object
- HttpPost httpPost = new HttpPost(slackEndpoint.getWebhookUrl());
-
- // Build Helper object
- SlackMessage slackMessage;
- Object payload = exchange.getIn().getBody();
- if (payload instanceof SlackMessage) {
- slackMessage = (SlackMessage) payload;
+ if (slackEndpoint.getToken() != null) {
+ return sendMessageByToken(exchange, callback);
} else {
- slackMessage = new SlackMessage();
- slackMessage.setText(exchange.getIn().getBody(String.class));
+ return sendMessageByWebhookURL(exchange, callback);
}
- slackMessage.setChannel(slackEndpoint.getChannel());
- slackMessage.setUsername(slackEndpoint.getUsername());
- slackMessage.setIconUrl(slackEndpoint.getIconUrl());
- slackMessage.setIconEmoji(slackEndpoint.getIconEmoji());
-
- // use charset from exchange or fallback to the default charset
- String charset = ExchangeHelper.getCharsetName(exchange, true);
-
- // Set the post body
- String json = asJson(slackMessage);
- StringEntity body = new StringEntity(json, charset);
+ }
- // Do the post
- httpPost.setEntity(body);
+ private boolean sendMessageByToken(Exchange exchange, AsyncCallback
callback) {
+ ChatPostMessageResponse response;
+ Object payload = exchange.getIn().getBody();
try {
- client.execute(httpPost, response -> {
- try {
- // 2xx is OK, anything else we regard as failure
- if (response.getStatusLine().getStatusCode() < 200 ||
response.getStatusLine().getStatusCode() > 299) {
- exchange.setException(
- new CamelExchangeException("Error POSTing to
Slack API: " + response.toString(), exchange));
- }
- EntityUtils.consumeQuietly(response.getEntity());
- } finally {
- callback.done(false);
- }
- return null;
- });
+ if (payload instanceof SlackMessage) {
+ response = sendLegacySlackMessage((SlackMessage) payload);
+ } else if (payload instanceof Message) {
+ response = sendMessage((Message) payload);
+ } else {
+ SlackMessage slackMessage = new SlackMessage();
+ slackMessage.setText(exchange.getIn().getBody(String.class));
+ response = sendLegacySlackMessage(slackMessage);
+ }
} catch (Exception e) {
exchange.setException(e);
- callback.done(true);
return true;
+ } finally {
+ callback.done(true);
+ }
+
+ if (!response.isOk()) {
+ exchange.setException(new CamelExchangeException("Error POSTing to
Slack API: " + response.toString(), exchange));
}
return false;
}
- /**
- * Returns a JSON string to be posted to the Slack API
- *
- * @return JSON string
- */
- public String asJson(SlackMessage message) {
- Map<String, Object> jsonMap = new HashMap<>();
-
- // Put the values in a map
- jsonMap.put(SlackConstants.SLACK_TEXT_FIELD, message.getText());
- jsonMap.put(SlackConstants.SLACK_CHANNEL_FIELD, message.getChannel());
- jsonMap.put(SlackConstants.SLACK_USERNAME_FIELD,
message.getUsername());
- jsonMap.put(SlackConstants.SLACK_ICON_URL_FIELD, message.getIconUrl());
- jsonMap.put(SlackConstants.SLACK_ICON_EMOJI_FIELD,
message.getIconEmoji());
-
- List<SlackMessage.Attachment> attachments = message.getAttachments();
- if (attachments != null && !attachments.isEmpty()) {
- buildAttachmentJson(jsonMap, attachments);
+ private ChatPostMessageResponse sendLegacySlackMessage(SlackMessage
slackMessage) throws IOException, SlackApiException {
+ return slack.methods(slackEndpoint.getToken()).chatPostMessage(req ->
req
+ .channel(slackEndpoint.getChannel())
+ .username(slackEndpoint.getUsername())
+ .iconUrl(slackEndpoint.getIconUrl())
+ .iconEmoji(slackEndpoint.getIconEmoji())
+ .text(slackMessage.getText()));
+ }
+
+ private ChatPostMessageResponse sendMessage(Message message) throws
IOException, SlackApiException {
+ return slack.methods(slackEndpoint.getToken()).chatPostMessage(req ->
req
+ .channel(slackEndpoint.getChannel())
+ .username(slackEndpoint.getUsername())
+ .iconUrl(slackEndpoint.getIconUrl())
+ .iconEmoji(slackEndpoint.getIconEmoji())
+ .text(message.getText())
+ .blocks(message.getBlocks())
+ .attachments(message.getAttachments()));
+ }
+
+ private boolean sendMessageByWebhookURL(Exchange exchange, AsyncCallback
callback) {
+ String json;
+ Object payload = exchange.getIn().getBody();
+ if (payload instanceof SlackMessage) {
+ json = GSON.toJson(addEndPointOptions((SlackMessage) payload));
+ } else if (payload instanceof Message) {
+ json = GSON.toJson(addEndPointOptions((Message) payload));
+ } else {
+ SlackMessage slackMessage = new SlackMessage();
+ slackMessage.setText(exchange.getIn().getBody(String.class));
+ json = GSON.toJson(addEndPointOptions(slackMessage));
+ }
+
+ WebhookResponse response;
+ try {
+ response = slack.send(slackEndpoint.getWebhookUrl(), json);
+ } catch (IOException e) {
+ exchange.setException(e);
+ return true;
+ } finally {
+ callback.done(true);
+ }
+
+ if (response.getCode() < 200 || response.getCode() > 299) {
+ exchange.setException(new CamelExchangeException("Error POSTing to
Slack API: " + response.toString(), exchange));
}
- // Return the string based on the JSON Object
- return new JsonObject(jsonMap).toJson();
+ return false;
}
- private void buildAttachmentJson(Map<String, Object> jsonMap,
List<SlackMessage.Attachment> attachments) {
- List<Map<String, Object>> attachmentsJson = new
ArrayList<>(attachments.size());
- attachments.forEach(attachment -> {
- Map<String, Object> attachmentJson = new HashMap<>();
- attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_FALLBACK_FIELD,
attachment.getFallback());
- attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_COLOR_FIELD,
attachment.getColor());
- attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_PRETEXT_FIELD,
attachment.getPretext());
-
attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_AUTHOR_NAME_FIELD,
attachment.getAuthorName());
-
attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_AUTHOR_LINK_FIELD,
attachment.getAuthorLink());
-
attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_AUTHOR_ICON_FIELD,
attachment.getAuthorIcon());
- attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_TITLE_FIELD,
attachment.getTitle());
-
attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_TITLE_LINK_FIELD,
attachment.getTitleLink());
- attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_TEXT_FIELD,
attachment.getText());
-
attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_IMAGE_URL_FIELD,
attachment.getImageUrl());
- attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_FOOTER_FIELD,
attachment.getFooter());
-
attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_FOOTER_ICON_FIELD,
attachment.getFooterIcon());
- attachmentJson.put(SlackConstants.SLACK_ATTACHMENT_TS_FIELD,
attachment.getTs());
-
- List<SlackMessage.Attachment.Field> fields =
attachment.getFields();
- if (fields != null && !fields.isEmpty()) {
- buildAttachmentFieldJson(attachmentJson, fields);
- }
- attachmentsJson.add(attachmentJson);
- });
- jsonMap.put(SlackConstants.SLACK_ATTACHMENTS_FIELD, attachmentsJson);
+ private Message addEndPointOptions(Message slackMessage) {
+ slackMessage.setChannel(slackEndpoint.getChannel());
+ slackMessage.setUsername(slackEndpoint.getUsername());
+ return slackMessage;
}
- private void buildAttachmentFieldJson(Map<String, Object> attachmentJson,
List<SlackMessage.Attachment.Field> fields) {
- List<Map<String, Object>> fieldsJson = new ArrayList<>(fields.size());
- fields.forEach(field -> {
- Map<String, Object> fieldJson = new HashMap<>();
- fieldJson.put("title", field.getTitle());
- fieldJson.put("value", field.getValue());
- fieldJson.put("short", field.isShortValue());
- fieldsJson.add(fieldJson);
- });
- attachmentJson.put("fields", fieldsJson);
+ private SlackMessage addEndPointOptions(SlackMessage slackMessage) {
+ slackMessage.setChannel(slackEndpoint.getChannel());
+ slackMessage.setUsername(slackEndpoint.getUsername());
+ slackMessage.setIconUrl(slackEndpoint.getIconUrl());
+ slackMessage.setIconEmoji(slackEndpoint.getIconEmoji());
+ return slackMessage;
}
}
diff --git
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/utils/SlackUtils.java
b/components/camel-slack/src/main/java/org/apache/camel/component/slack/utils/SlackUtils.java
deleted file mode 100644
index 0515cdb..0000000
---
a/components/camel-slack/src/main/java/org/apache/camel/component/slack/utils/SlackUtils.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.camel.component.slack.utils;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.StandardCharsets;
-
-import org.apache.http.HttpResponse;
-
-public final class SlackUtils {
-
- private SlackUtils() {
- }
-
- public static String readResponse(HttpResponse response) throws
IOException {
- ByteArrayOutputStream result = new ByteArrayOutputStream();
- try (InputStream s = response.getEntity().getContent()) {
- byte[] buffer = new byte[1024];
- int length;
- while ((length = s.read(buffer)) != -1) {
- result.write(buffer, 0, length);
- }
- }
- return result.toString(StandardCharsets.UTF_8.name());
- }
-
- public static String readResponse(InputStream s) throws IOException,
UnsupportedEncodingException {
- ByteArrayOutputStream result = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int length;
- while ((length = s.read(buffer)) != -1) {
- result.write(buffer, 0, length);
- }
- return result.toString(StandardCharsets.UTF_8.name());
- }
-}
diff --git
a/components/camel-slack/src/test/java/org/apache/camel/component/slack/SlackProducerTest.java
b/components/camel-slack/src/test/java/org/apache/camel/component/slack/SlackProducerTest.java
index 93c7080..406490b 100644
---
a/components/camel-slack/src/test/java/org/apache/camel/component/slack/SlackProducerTest.java
+++
b/components/camel-slack/src/test/java/org/apache/camel/component/slack/SlackProducerTest.java
@@ -16,6 +16,11 @@
*/
package org.apache.camel.component.slack;
+import java.util.Collections;
+
+import com.slack.api.model.Message;
+import com.slack.api.model.block.SectionBlock;
+import com.slack.api.model.block.composition.MarkdownTextObject;
import org.apache.camel.EndpointInject;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.direct.DirectEndpoint;
@@ -43,6 +48,24 @@ public class SlackProducerTest extends CamelTestSupport {
assertMockEndpointsSatisfied();
}
+ @Test
+ public void testSlackAPIModelMessage() throws Exception {
+ errors.expectedMessageCount(0);
+
+ Message message = new Message();
+ message.setBlocks(Collections.singletonList(SectionBlock
+ .builder()
+ .text(MarkdownTextObject
+ .builder()
+ .text("*Hello from Camel!*")
+ .build())
+ .build()));
+
+ template.sendBody(test, message);
+
+ assertMockEndpointsSatisfied();
+ }
+
@Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
@@ -54,10 +77,10 @@ public class SlackProducerTest extends CamelTestSupport {
onException(Exception.class).handled(true).to(errors);
- final String slacUser = System.getProperty("SLACK_USER",
"CamelTest");
+ final String slackUser = System.getProperty("SLACK_USER",
"CamelTest");
from("undertow:http://localhost:" + UNDERTOW_PORT +
"/slack/webhook").setBody(constant("{\"ok\": true}"));
-
from(test).to(String.format("slack:#general?iconEmoji=:camel:&username=%s",
slacUser));
+
from(test).to(String.format("slack:#general?iconEmoji=:camel:&username=%s",
slackUser));
}
};
}
diff --git
a/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SlackComponentBuilderFactory.java
b/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SlackComponentBuilderFactory.java
index ed9ad93..b38560d 100644
---
a/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SlackComponentBuilderFactory.java
+++
b/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/SlackComponentBuilderFactory.java
@@ -115,6 +115,20 @@ public interface SlackComponentBuilderFactory {
return this;
}
/**
+ * The token to use.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: token
+ *
+ * @param token the value to set
+ * @return the dsl builder
+ */
+ default SlackComponentBuilder token(java.lang.String token) {
+ doSetProperty("token", token);
+ return this;
+ }
+ /**
* The incoming webhook URL.
*
* The option is a: <code>java.lang.String</code> type.
@@ -148,6 +162,7 @@ public interface SlackComponentBuilderFactory {
case "bridgeErrorHandler": ((SlackComponent)
component).setBridgeErrorHandler((boolean) value); return true;
case "lazyStartProducer": ((SlackComponent)
component).setLazyStartProducer((boolean) value); return true;
case "autowiredEnabled": ((SlackComponent)
component).setAutowiredEnabled((boolean) value); return true;
+ case "token": ((SlackComponent)
component).setToken((java.lang.String) value); return true;
case "webhookUrl": ((SlackComponent)
component).setWebhookUrl((java.lang.String) value); return true;
default: return false;
}
diff --git
a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SlackEndpointBuilderFactory.java
b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SlackEndpointBuilderFactory.java
index 84e7983..d062915 100644
---
a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SlackEndpointBuilderFactory.java
+++
b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SlackEndpointBuilderFactory.java
@@ -47,6 +47,20 @@ public interface SlackEndpointBuilderFactory {
return (AdvancedSlackEndpointConsumerBuilder) this;
}
/**
+ * The token to use.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: common
+ *
+ * @param token the value to set
+ * @return the dsl builder
+ */
+ default SlackEndpointConsumerBuilder token(String token) {
+ doSetProperty("token", token);
+ return this;
+ }
+ /**
* Allows for bridging the consumer to the Camel routing Error Handler,
* which mean any exceptions occurred while the consumer is trying to
* pickup incoming messages, or the likes, will now be processed as a
@@ -92,6 +106,40 @@ public interface SlackEndpointBuilderFactory {
return this;
}
/**
+ * Type of conversation.
+ *
+ * The option is a:
+ * <code>com.slack.api.model.ConversationType</code> type.
+ *
+ * Default: PUBLIC_CHANNEL
+ * Group: consumer
+ *
+ * @param conversationType the value to set
+ * @return the dsl builder
+ */
+ default SlackEndpointConsumerBuilder conversationType(
+ ConversationType conversationType) {
+ doSetProperty("conversationType", conversationType);
+ return this;
+ }
+ /**
+ * Type of conversation.
+ *
+ * The option will be converted to a
+ * <code>com.slack.api.model.ConversationType</code> type.
+ *
+ * Default: PUBLIC_CHANNEL
+ * Group: consumer
+ *
+ * @param conversationType the value to set
+ * @return the dsl builder
+ */
+ default SlackEndpointConsumerBuilder conversationType(
+ String conversationType) {
+ doSetProperty("conversationType", conversationType);
+ return this;
+ }
+ /**
* The Max Result for the poll.
*
* The option is a: <code>java.lang.String</code> type.
@@ -107,6 +155,37 @@ public interface SlackEndpointBuilderFactory {
return this;
}
/**
+ * Create exchanges in natural order (oldest to newest) or not.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: consumer
+ *
+ * @param naturalOrder the value to set
+ * @return the dsl builder
+ */
+ default SlackEndpointConsumerBuilder naturalOrder(boolean
naturalOrder) {
+ doSetProperty("naturalOrder", naturalOrder);
+ return this;
+ }
+ /**
+ * Create exchanges in natural order (oldest to newest) or not.
+ *
+ * The option will be converted to a <code>boolean</code>
+ * type.
+ *
+ * Default: false
+ * Group: consumer
+ *
+ * @param naturalOrder the value to set
+ * @return the dsl builder
+ */
+ default SlackEndpointConsumerBuilder naturalOrder(String naturalOrder)
{
+ doSetProperty("naturalOrder", naturalOrder);
+ return this;
+ }
+ /**
* If the polling consumer did not poll any files, you can enable this
* option to send an empty message (no body) instead.
*
@@ -157,20 +236,6 @@ public interface SlackEndpointBuilderFactory {
return this;
}
/**
- * The token to use.
- *
- * The option is a: <code>java.lang.String</code> type.
- *
- * Group: consumer
- *
- * @param token the value to set
- * @return the dsl builder
- */
- default SlackEndpointConsumerBuilder token(String token) {
- doSetProperty("token", token);
- return this;
- }
- /**
* The number of subsequent error polls (failed due some error) that
* should happen before the backoffMultipler should kick-in.
*
@@ -776,6 +841,20 @@ public interface SlackEndpointBuilderFactory {
return (AdvancedSlackEndpointProducerBuilder) this;
}
/**
+ * The token to use.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: common
+ *
+ * @param token the value to set
+ * @return the dsl builder
+ */
+ default SlackEndpointProducerBuilder token(String token) {
+ doSetProperty("token", token);
+ return this;
+ }
+ /**
* Use a Slack emoji as an avatar.
*
* The option is a: <code>java.lang.String</code> type.
@@ -908,6 +987,20 @@ public interface SlackEndpointBuilderFactory {
default AdvancedSlackEndpointBuilder advanced() {
return (AdvancedSlackEndpointBuilder) this;
}
+ /**
+ * The token to use.
+ *
+ * The option is a: <code>java.lang.String</code> type.
+ *
+ * Group: common
+ *
+ * @param token the value to set
+ * @return the dsl builder
+ */
+ default SlackEndpointBuilder token(String token) {
+ doSetProperty("token", token);
+ return this;
+ }
}
/**
@@ -922,6 +1015,16 @@ public interface SlackEndpointBuilderFactory {
}
}
+ /**
+ * Proxy enum for <code>com.slack.api.model.ConversationType</code> enum.
+ */
+ enum ConversationType {
+ PUBLIC_CHANNEL,
+ PRIVATE_CHANNEL,
+ MPIM,
+ IM;
+ }
+
public interface SlackBuilders {
/**
* Slack (camel-slack)
diff --git a/docs/components/modules/ROOT/pages/slack-component.adoc
b/docs/components/modules/ROOT/pages/slack-component.adoc
index 6c0cc29..b88ad4d 100644
--- a/docs/components/modules/ROOT/pages/slack-component.adoc
+++ b/docs/components/modules/ROOT/pages/slack-component.adoc
@@ -46,7 +46,7 @@ To send a direct message to a slackuser.
[source,java]
-------------------------
-slack:@username[?options]
+slack:@userID[?options]
-------------------------
== Options
@@ -54,7 +54,7 @@ slack:@username[?options]
// component options: START
-The Slack component supports 4 options, which are listed below.
+The Slack component supports 5 options, which are listed below.
@@ -64,6 +64,7 @@ The Slack component supports 4 options, which are listed
below.
| *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions occurred while the
consumer is trying to pickup incoming messages, or the likes, will now be
processed as a message and handled by the routing Error Handler. By default the
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with
exceptions, that will be logged at WARN or ERROR level and ignored. | false |
boolean
| *lazyStartProducer* (producer) | Whether the producer should be started lazy
(on the first message). By starting lazy you can use this to allow CamelContext
and routes to startup in situations where a producer may otherwise fail during
starting and cause the route to fail being started. By deferring this startup
to be lazy then the startup failure can be handled during routing messages via
Camel's routing error handlers. Beware that when the first message is processed
then creating and [...]
| *autowiredEnabled* (advanced) | Whether autowiring is enabled. This is used
for automatic autowiring options (the option must be marked as autowired) by
looking up in the registry to find if there is a single instance of matching
type, which then gets configured on the component. This can be used for
automatic configuring JDBC data sources, JMS connection factories, AWS Clients,
etc. | true | boolean
+| *token* (token) | The token to use | | String
| *webhookUrl* (webhook) | The incoming webhook URL | | String
|===
// component options: END
@@ -90,17 +91,19 @@ with the following path and query parameters:
|===
-=== Query Parameters (27 parameters):
+=== Query Parameters (29 parameters):
[width="100%",cols="2,5,^1,2",options="header"]
|===
| Name | Description | Default | Type
+| *token* (common) | The token to use | | String
| *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions occurred while the
consumer is trying to pickup incoming messages, or the likes, will now be
processed as a message and handled by the routing Error Handler. By default the
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with
exceptions, that will be logged at WARN or ERROR level and ignored. | false |
boolean
+| *conversationType* (consumer) | Type of conversation. There are 4 enums and
the value can be one of: PUBLIC_CHANNEL, PRIVATE_CHANNEL, MPIM, IM |
PUBLIC_CHANNEL | ConversationType
| *maxResults* (consumer) | The Max Result for the poll | 10 | String
+| *naturalOrder* (consumer) | Create exchanges in natural order (oldest to
newest) or not | false | boolean
| *sendEmptyMessageWhenIdle* (consumer) | If the polling consumer did not poll
any files, you can enable this option to send an empty message (no body)
instead. | false | boolean
| *serverUrl* (consumer) | The Server URL of the Slack instance |
https://slack.com | String
-| *token* (consumer) | The token to use | | String
| *exceptionHandler* (consumer) | To let the consumer use a custom
ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this
option is not in use. By default the consumer will deal with exceptions, that
will be logged at WARN or ERROR level and ignored. | | ExceptionHandler
| *exchangePattern* (consumer) | Sets the exchange pattern when the consumer
creates an exchange. There are 3 enums and the value can be one of: InOnly,
InOut, InOptionalOut | | ExchangePattern
| *pollStrategy* (consumer) | A pluggable
org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your
custom implementation to control error handling usually occurred during the
poll operation before an Exchange have been created and being routed in Camel.
| | PollingConsumerPollStrategy
@@ -131,13 +134,14 @@ with the following path and query parameters:
== SlackComponent
The SlackComponent with XML must be configured as a Spring or Blueprint
-bean that contains the incoming webhook url for the integration as a
+bean that contains the incoming webhook url or the app token for the
integration as a
parameter.
[source,xml]
-----------------------------------------------------------------------------------------------------------------------
<bean id="slack" class="org.apache.camel.component.slack.SlackComponent">
<property name="webhookUrl"
value="https://hooks.slack.com/services/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"/>
+ <property name="token"
value="xoxb-12345678901-1234567890123-xxxxxxxxxxxxxxxxxxxxxxxx"/>
</bean>
-----------------------------------------------------------------------------------------------------------------------
@@ -166,6 +170,35 @@ A CamelContext with Blueprint could be as:
</blueprint>
---------------------------------------------------------------------------------------------------------------------------
+== Producer
+
+*Since Camel 3.9.0* +
+ You can now use a token to send a message instead of WebhookUrl
+
+[source,java]
+---------------------------------------------------------------------------------------------------------------------------
+from("direct:test")
+ .to("slack:#random?token=RAW(<YOUR_TOKEN>)");
+---------------------------------------------------------------------------------------------------------------------------
+
+ You can now use the Slack API model to create blocks. You can read more about
it here https://api.slack.com/block-kit
+
+[source,java]
+---------------------------------------------------------------------------------------------------------------------------
+ public void testSlackAPIModelMessage() {
+ Message message = new Message();
+ message.setBlocks(Collections.singletonList(SectionBlock
+ .builder()
+ .text(MarkdownTextObject
+ .builder()
+ .text("*Hello from Camel!*")
+ .build())
+ .build()));
+
+ template.sendBody(test, message);
+ }
+---------------------------------------------------------------------------------------------------------------------------
+
== Consumer
You can use also a consumer for messages in channel
@@ -182,6 +215,15 @@ You'll need to create a Slack app and use it on your
workspace.
Use the 'Bot User OAuth Access Token' as token for the consumer endpoint.
-IMPORTANT: Add the `channels:history` and `channels:read` user token scope to
your app to grant it permission to view messages in the user's public channels.
+IMPORTANT: Add the corresponding history (`channels:history` or
`groups:history` or `mpim:history` or `im:history`) and
+read (`channels:read` or `groups:read` or `mpim:read` or `im:read`) user token
scope to your app to grant it permission to
+view messages in the corresponding channel. You will need to use the
conversationType option to set it up too (`PUBLIC_CHANNEL`, `PRIVATE_CHANNEL`,
`MPIM`, `IM`)
+
+*Since Camel 3.9.0* +
+ The naturalOrder option allows consuming messages from the oldest to the
newest.
+Originally you would get the newest first and consume backward (message 3 =>
message 2 => message 1)
+
+IMPORTANT: You can use the conversationType option to read history and
messages from a channel that is not only public
+(`PUBLIC_CHANNEL`,`PRIVATE_CHANNEL`, `MPIM`, `IM`)
include::camel-spring-boot::page$slack-starter.adoc[]
diff --git a/parent/pom.xml b/parent/pom.xml
index 84f8384..85928f0 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -493,6 +493,7 @@
<shrinkwrap-resolver-version>3.1.3</shrinkwrap-resolver-version>
<shrinkwrap-version>1.2.6</shrinkwrap-version>
<sip-api-version>1.1</sip-api-version>
+ <slack-api-model-version>1.6.1</slack-api-model-version>
<slf4j-api-version>1.7.30</slf4j-api-version>
<slf4j-version>1.7.30</slf4j-version>
<smack-version>4.3.5</smack-version>