This is an automated email from the ASF dual-hosted git repository.

davsclaus 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 cfd584a  CAMEL-15319: Add 'watchUpdates' consumer to camel-jira (#4137)
cfd584a is described below

commit cfd584ac2da954c9e2a905e2928c86f7ac804ea3
Author: Matej Melko <6814482+mme...@users.noreply.github.com>
AuthorDate: Fri Aug 28 10:06:46 2020 +0200

    CAMEL-15319: Add 'watchUpdates' consumer to camel-jira (#4137)
    
    * CAMEL-JIRA: Add consumer to watch updates in issues defined by jql.
    Add tests for the new consumer.
    
    * CAMEL-JIRA: Update the documentation to include watchUpdates consumer.
---
 .../component/jira/JiraEndpointConfigurer.java     |  10 ++
 .../org/apache/camel/component/jira/jira.json      |   4 +-
 .../camel-jira/src/main/docs/jira-component.adoc   |  17 ++-
 .../apache/camel/component/jira/JiraEndpoint.java  |  34 ++++-
 .../org/apache/camel/component/jira/JiraType.java  |   2 +
 .../jira/consumer/WatchUpdatesConsumer.java        | 113 ++++++++++++++
 .../jira/JiraComponentConfigurationTest.java       |  21 +++
 .../camel/component/jira/JiraTestConstants.java    |   1 +
 .../org/apache/camel/component/jira/Utils.java     |  19 +++
 .../jira/consumer/WatchUpdatesConsumerTest.java    | 168 +++++++++++++++++++++
 .../builder/endpoint/StaticEndpointBuilders.java   |   8 +-
 .../endpoint/dsl/JiraEndpointBuilderFactory.java   |  49 +++++-
 12 files changed, 434 insertions(+), 12 deletions(-)

diff --git 
a/components/camel-jira/src/generated/java/org/apache/camel/component/jira/JiraEndpointConfigurer.java
 
b/components/camel-jira/src/generated/java/org/apache/camel/component/jira/JiraEndpointConfigurer.java
index 40f3c9e..5c56c16 100644
--- 
a/components/camel-jira/src/generated/java/org/apache/camel/component/jira/JiraEndpointConfigurer.java
+++ 
b/components/camel-jira/src/generated/java/org/apache/camel/component/jira/JiraEndpointConfigurer.java
@@ -42,10 +42,14 @@ public class JiraEndpointConfigurer extends 
PropertyConfigurerSupport implements
         case "password": 
target.getConfiguration().setPassword(property(camelContext, 
java.lang.String.class, value)); return true;
         case "privatekey":
         case "privateKey": 
target.getConfiguration().setPrivateKey(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "sendonlyupdatedfield":
+        case "sendOnlyUpdatedField": 
target.setSendOnlyUpdatedField(property(camelContext, boolean.class, value)); 
return true;
         case "synchronous": target.setSynchronous(property(camelContext, 
boolean.class, value)); return true;
         case "username": 
target.getConfiguration().setUsername(property(camelContext, 
java.lang.String.class, value)); return true;
         case "verificationcode":
         case "verificationCode": 
target.getConfiguration().setVerificationCode(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "watchedfields":
+        case "watchedFields": target.setWatchedFields(property(camelContext, 
java.lang.String.class, value)); return true;
         default: return false;
         }
     }
@@ -66,9 +70,11 @@ public class JiraEndpointConfigurer extends 
PropertyConfigurerSupport implements
         answer.put("maxResults", java.lang.Integer.class);
         answer.put("password", java.lang.String.class);
         answer.put("privateKey", java.lang.String.class);
+        answer.put("sendOnlyUpdatedField", boolean.class);
         answer.put("synchronous", boolean.class);
         answer.put("username", java.lang.String.class);
         answer.put("verificationCode", java.lang.String.class);
+        answer.put("watchedFields", java.lang.String.class);
         return answer;
     }
 
@@ -99,10 +105,14 @@ public class JiraEndpointConfigurer extends 
PropertyConfigurerSupport implements
         case "password": return target.getConfiguration().getPassword();
         case "privatekey":
         case "privateKey": return target.getConfiguration().getPrivateKey();
+        case "sendonlyupdatedfield":
+        case "sendOnlyUpdatedField": return target.isSendOnlyUpdatedField();
         case "synchronous": return target.isSynchronous();
         case "username": return target.getConfiguration().getUsername();
         case "verificationcode":
         case "verificationCode": return 
target.getConfiguration().getVerificationCode();
+        case "watchedfields":
+        case "watchedFields": return target.getWatchedFields();
         default: return null;
         }
     }
diff --git 
a/components/camel-jira/src/generated/resources/org/apache/camel/component/jira/jira.json
 
b/components/camel-jira/src/generated/resources/org/apache/camel/component/jira/jira.json
index d35a9a4..b802834 100644
--- 
a/components/camel-jira/src/generated/resources/org/apache/camel/component/jira/jira.json
+++ 
b/components/camel-jira/src/generated/resources/org/apache/camel/component/jira/jira.json
@@ -35,12 +35,14 @@
     "verificationCode": { "kind": "property", "displayName": "Verification 
Code", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "secret": true, 
"configurationClass": "org.apache.camel.component.jira.JiraConfiguration", 
"configurationField": "configuration", "description": "(OAuth only) The 
verification code from Jira generated in the first step of the authorization 
proccess." }
   },
   "properties": {
-    "type": { "kind": "path", "displayName": "Type", "group": "common", 
"label": "", "required": true, "type": "object", "javaType": 
"org.apache.camel.component.jira.JiraType", "enum": [ "ADDCOMMENT", "ADDISSUE", 
"ATTACH", "DELETEISSUE", "NEWISSUES", "NEWCOMMENTS", "UPDATEISSUE", 
"TRANSITIONISSUE", "WATCHERS", "ADDISSUELINK", "ADDWORKLOG", "FETCHISSUE", 
"FETCHCOMMENTS" ], "deprecated": false, "deprecationNote": "", "secret": false, 
"description": "Operation to perform. Consumers: NewIssu [...]
+    "type": { "kind": "path", "displayName": "Type", "group": "common", 
"label": "", "required": true, "type": "object", "javaType": 
"org.apache.camel.component.jira.JiraType", "enum": [ "ADDCOMMENT", "ADDISSUE", 
"ATTACH", "DELETEISSUE", "NEWISSUES", "NEWCOMMENTS", "WATCHUPDATES", 
"UPDATEISSUE", "TRANSITIONISSUE", "WATCHERS", "ADDISSUELINK", "ADDWORKLOG", 
"FETCHISSUE", "FETCHCOMMENTS" ], "deprecated": false, "deprecationNote": "", 
"secret": false, "description": "Operation to perform. Co [...]
     "delay": { "kind": "parameter", "displayName": "Delay", "group": "common", 
"label": "", "required": false, "type": "integer", "javaType": 
"java.lang.Integer", "deprecated": false, "secret": false, "defaultValue": 
"6000", "configurationClass": 
"org.apache.camel.component.jira.JiraConfiguration", "configurationField": 
"configuration", "description": "Time in milliseconds to elapse for the next 
poll." },
     "jiraUrl": { "kind": "parameter", "displayName": "Jira Url", "group": 
"common", "label": "", "required": true, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "deprecationNote": "", "secret": 
false, "configurationClass": 
"org.apache.camel.component.jira.JiraConfiguration", "configurationField": 
"configuration", "description": "The Jira server url, example: 
http:\/\/my_jira.com:8081" },
     "bridgeErrorHandler": { "kind": "parameter", "displayName": "Bridge Error 
Handler", "group": "consumer", "label": "consumer", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": 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 message and handled b [...]
     "jql": { "kind": "parameter", "displayName": "Jql", "group": "consumer", 
"label": "consumer", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "secret": false, "description": "JQL 
is the query language from JIRA which allows you to retrieve the data you want. 
For example jql=project=MyProject Where MyProject is the product key in Jira. 
It is important to use the RAW() and set the JQL inside it to prevent camel 
parsing it, example: RAW(project  [...]
     "maxResults": { "kind": "parameter", "displayName": "Max Results", 
"group": "consumer", "label": "consumer", "required": false, "type": "integer", 
"javaType": "java.lang.Integer", "deprecated": false, "secret": false, 
"defaultValue": "50", "description": "Max number of issues to search for" },
+    "sendOnlyUpdatedField": { "kind": "parameter", "displayName": "Send Only 
Updated Field", "group": "consumer", "label": "consumer", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, 
"defaultValue": "true", "description": "Indicator for sending only changed 
fields in exchange body or issue object. By default consumer sends only changed 
fields." },
+    "watchedFields": { "kind": "parameter", "displayName": "Watched Fields", 
"group": "consumer", "label": "consumer", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "secret": false, 
"defaultValue": "Status,Priority", "description": "Comma separated list of 
fields to watch for changes. Status,Priority are the defaults." },
     "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, "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 consumer will deal with [...]
     "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, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
     "lazyStartProducer": { "kind": "parameter", "displayName": "Lazy Start 
Producer", "group": "producer", "label": "producer", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": 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 starting and cause the  [...]
diff --git a/components/camel-jira/src/main/docs/jira-component.adoc 
b/components/camel-jira/src/main/docs/jira-component.adoc
index 8cb2d44..fd2d3a6 100644
--- a/components/camel-jira/src/main/docs/jira-component.adoc
+++ b/components/camel-jira/src/main/docs/jira-component.adoc
@@ -78,6 +78,7 @@ For consumers:
 
 * newIssues: retrieve only new issues after the route is started
 * newComments: retrieve only new comments after the route is started
+* watchUpdates: retrieve only updated fields/issues based on provided jql
 
 For producers:
 
@@ -106,11 +107,11 @@ with the following path and query parameters:
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
 | Name | Description | Default | Type
-| *type* | *Required* Operation to perform. Consumers: NewIssues, NewComments. 
Producers: AddIssue, AttachFile, DeleteIssue, TransitionIssue, UpdateIssue, 
Watchers. See this class javadoc description for more information. The value 
can be one of: ADDCOMMENT, ADDISSUE, ATTACH, DELETEISSUE, NEWISSUES, 
NEWCOMMENTS, UPDATEISSUE, TRANSITIONISSUE, WATCHERS, ADDISSUELINK, ADDWORKLOG, 
FETCHISSUE, FETCHCOMMENTS |  | JiraType
+| *type* | *Required* Operation to perform. Consumers: NewIssues, NewComments. 
Producers: AddIssue, AttachFile, DeleteIssue, TransitionIssue, UpdateIssue, 
Watchers. See this class javadoc description for more information. The value 
can be one of: ADDCOMMENT, ADDISSUE, ATTACH, DELETEISSUE, NEWISSUES, 
NEWCOMMENTS, WATCHUPDATES, UPDATEISSUE, TRANSITIONISSUE, WATCHERS, 
ADDISSUELINK, ADDWORKLOG, FETCHISSUE, FETCHCOMMENTS |  | JiraType
 |===
 
 
-=== Query Parameters (16 parameters):
+=== Query Parameters (18 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -121,6 +122,8 @@ with the following path and query parameters:
 | *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
 | *jql* (consumer) | JQL is the query language from JIRA which allows you to 
retrieve the data you want. For example jql=project=MyProject Where MyProject 
is the product key in Jira. It is important to use the RAW() and set the JQL 
inside it to prevent camel parsing it, example: RAW(project in (MYP, COM) AND 
resolution = Unresolved) |  | String
 | *maxResults* (consumer) | Max number of issues to search for | 50 | Integer
+| *sendOnlyUpdatedField* (consumer) | Indicator for sending only changed 
fields in exchange body or issue object. By default consumer sends only changed 
fields. | true | boolean
+| *watchedFields* (consumer) | Comma separated list of fields to watch for 
changes. Status,Priority are the defaults. | Status,Priority | 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. The value can be one of: InOnly, InOut, InOptionalOut |  | 
ExchangePattern
 | *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 [...]
@@ -267,5 +270,15 @@ Required:
 * `IssueWatchersAdd`: A list of strings with the usernames to add to the 
watcher list.
 * `IssueWatchersRemove`: A list of strings with the usernames to remove from 
the watcher list.
 
+== WatchUpdates (consumer)
+* `watchedFields` Comma separated list of fields to watch for changes i.e 
`Status,Priority,Assignee,Components` etc.
+* `sendOnlyUpdatedField` By default only changed field is send as the body.
+
+All messages also contain following headers that add additional info about the 
change:
+
+* `issueKey`: Key of the updated issue
+* `changed`: name of the updated field (i.e Status)
+* `watchedIssues`: list of all issue keys that are watched in the time of 
update
+
 
 include::camel-spring-boot::page$jira-starter.adoc[]
diff --git 
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/JiraEndpoint.java
 
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/JiraEndpoint.java
index 6128c3f..1cbe6b9 100644
--- 
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/JiraEndpoint.java
+++ 
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/JiraEndpoint.java
@@ -26,6 +26,7 @@ import org.apache.camel.Processor;
 import org.apache.camel.Producer;
 import org.apache.camel.component.jira.consumer.NewCommentsConsumer;
 import org.apache.camel.component.jira.consumer.NewIssuesConsumer;
+import org.apache.camel.component.jira.consumer.WatchUpdatesConsumer;
 import org.apache.camel.component.jira.oauth.JiraOAuthAuthenticationHandler;
 import 
org.apache.camel.component.jira.oauth.OAuthAsynchronousJiraRestClientFactory;
 import org.apache.camel.component.jira.producer.AddCommentProducer;
@@ -57,7 +58,8 @@ import static 
org.apache.camel.component.jira.JiraConstants.JIRA_REST_CLIENT_FAC
  * include:
  * <p>
  * CONSUMERS jira://newIssues (retrieve only new issues after the route is 
started) jira://newComments (retrieve only
- * new comments after the route is started)
+ * new comments after the route is started) jira://watchChanges (retrieve only 
defined changes in issues picked base on
+ * provided jql)
  * <p>
  * PRODUCERS jira://addIssue (add an issue) jira://addComment (add a comment 
on a given issue) jira://attach (add an
  * attachment on a given issue) jira://deleteIssue (delete a given issue) 
jira://updateIssue (update fields of a given
@@ -82,6 +84,10 @@ public class JiraEndpoint extends DefaultEndpoint {
     private JiraType type;
     @UriParam(label = "consumer")
     private String jql;
+    @UriParam(label = "consumer", defaultValue = "Status,Priority")
+    private String watchedFields = "Status,Priority";
+    @UriParam(label = "consumer", defaultValue = "true")
+    private boolean sendOnlyUpdatedField = true;
     @UriParam(label = "consumer", defaultValue = "50")
     private Integer maxResults = 50;
     @UriParam
@@ -167,6 +173,8 @@ public class JiraEndpoint extends DefaultEndpoint {
             consumer = new NewCommentsConsumer(this, processor);
         } else if (type == JiraType.NEWISSUES) {
             consumer = new NewIssuesConsumer(this, processor);
+        } else if (type == JiraType.WATCHUPDATES) {
+            consumer = new WatchUpdatesConsumer(this, processor);
         } else {
             throw new IllegalArgumentException("Consumer does not support 
type: " + type);
         }
@@ -222,4 +230,28 @@ public class JiraEndpoint extends DefaultEndpoint {
     public void setMaxResults(Integer maxResults) {
         this.maxResults = maxResults;
     }
+
+    /**
+     * Comma separated list of fields to watch for changes. "Status,Priority" 
are the defaults.
+     */
+    public String getWatchedFields() {
+        return watchedFields;
+    }
+
+    public void setWatchedFields(String watchChange) {
+        this.watchedFields = watchChange;
+    }
+
+    /**
+     * Indicator for sending only changed fields in exchange body or issue 
object. By default consumer sends only
+     * changed fields.
+     */
+    public boolean isSendOnlyUpdatedField() {
+        return sendOnlyUpdatedField;
+    }
+
+    public void setSendOnlyUpdatedField(boolean sendOnlyUpdatedField) {
+        this.sendOnlyUpdatedField = sendOnlyUpdatedField;
+    }
+
 }
diff --git 
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/JiraType.java
 
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/JiraType.java
index 363fca8..616d4d9 100644
--- 
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/JiraType.java
+++ 
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/JiraType.java
@@ -30,6 +30,8 @@ public enum JiraType {
     NEWISSUES,
     // retrieve recent comments from any issues
     NEWCOMMENTS,
+
+    WATCHUPDATES,
     // update the fields of an issue
     UPDATEISSUE,
     // transition a status and resolution of an issue
diff --git 
a/components/camel-jira/src/main/java/org/apache/camel/component/jira/consumer/WatchUpdatesConsumer.java
 
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/consumer/WatchUpdatesConsumer.java
new file mode 100644
index 0000000..417de78
--- /dev/null
+++ 
b/components/camel-jira/src/main/java/org/apache/camel/component/jira/consumer/WatchUpdatesConsumer.java
@@ -0,0 +1,113 @@
+/*
+ * 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.jira.consumer;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+import com.atlassian.jira.rest.client.api.domain.Issue;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.component.jira.JiraEndpoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WatchUpdatesConsumer extends AbstractJiraConsumer {
+
+    private static final transient Logger LOG = 
LoggerFactory.getLogger(WatchUpdatesConsumer.class);
+    HashMap<Long, Issue> watchedIssues;
+    List<String> watchedFieldsList;
+    String watchedIssuesKeys;
+
+    public WatchUpdatesConsumer(JiraEndpoint endpoint, Processor processor) {
+        super(endpoint, processor);
+        this.watchedFieldsList = new ArrayList<>();
+        this.watchedFieldsList = 
Arrays.asList(endpoint.getWatchedFields().split(","));
+        initIssues();
+    }
+
+    private void initIssues() {
+        watchedIssues = new HashMap<>();
+        List<Issue> issues = getIssues(((JiraEndpoint) 
getEndpoint()).getJql(), 0, 50,
+                ((JiraEndpoint) getEndpoint()).getMaxResults());
+        issues.forEach(i -> watchedIssues.put(i.getId(), i));
+        watchedIssuesKeys = issues.stream()
+                .map(Issue::getKey)
+                .collect(Collectors.joining(","));
+    }
+
+    @Override
+    protected int poll() throws Exception {
+        List<Issue> issues = getIssues(((JiraEndpoint) 
getEndpoint()).getJql(), 0, 50,
+                ((JiraEndpoint) getEndpoint()).getMaxResults());
+        if (watchedIssues.values().size() != issues.size()) {
+            init();
+        }
+        for (Issue issue : issues) {
+            checkIfIssueChanged(issue);
+        }
+        return 0;
+    }
+
+    private void checkIfIssueChanged(Issue issue) throws Exception {
+        Issue original = watchedIssues.get(issue.getId());
+        AtomicBoolean issueChanged = new AtomicBoolean(false);
+        if (original != null) {
+            for (String field : this.watchedFieldsList) {
+                if (hasFieldChanged(issue, original, field)) {
+                    issueChanged.set(true);
+                }
+            }
+            if (issueChanged.get()) {
+                watchedIssues.put(issue.getId(), issue);
+            }
+        }
+    }
+
+    private boolean hasFieldChanged(Issue changed, Issue original, String 
fieldName) throws Exception {
+        Method get = Issue.class.getDeclaredMethod("get" + fieldName);
+        Object originalField = get.invoke(original);
+        Object changedField = get.invoke(changed);
+
+        if (!Objects.equals(originalField, changedField)) {
+            if (!((JiraEndpoint) getEndpoint()).isSendOnlyUpdatedField()) {
+                processExchange(changed, changed.getKey(), fieldName);
+            } else {
+                processExchange(changedField, changed.getKey(), fieldName);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void processExchange(Object body, String issueKey, String changed) 
throws Exception {
+        Exchange e = getEndpoint().createExchange();
+        e.getIn().setBody(body);
+        e.getIn().setHeader("issueKey", issueKey);
+        e.getIn().setHeader("changed", changed);
+        e.getIn().setHeader("watchedIssues", watchedIssuesKeys);
+        LOG.debug(" {}: {} changed to {}", issueKey, changed, body);
+        getProcessor().process(e);
+    }
+}
diff --git 
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/JiraComponentConfigurationTest.java
 
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/JiraComponentConfigurationTest.java
index 06c004c..89081d5 100644
--- 
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/JiraComponentConfigurationTest.java
+++ 
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/JiraComponentConfigurationTest.java
@@ -38,6 +38,10 @@ public class JiraComponentConfigurationTest extends 
CamelTestSupport {
     private static final String PRIV_KEY_VALUE = "my_privateKey_test";
     private static final String JIRA_URL = "jiraUrl";
     private static final String JIRA_URL_VALUE = "http://my_jira_server:8080";;
+    private static final String WATCHED_FIELDS = "watchedFields";
+    private static final String WATCHED_FIELDS_VALUE = 
"Assignee,Components,Priority";
+    private static final String SEND_ONLY_CHANGED_FIELD = 
"sendOnlyUpdatedField";
+    private static final boolean SEND_ONLY_CHANGED_FIELD_VALUE = false;
 
     @Test
     public void createEndpointWithBasicAuthentication() throws Exception {
@@ -75,6 +79,23 @@ public class JiraComponentConfigurationTest extends 
CamelTestSupport {
         assertEquals(PRIV_KEY_VALUE, 
endpoint.getConfiguration().getPrivateKey());
     }
 
+    @Test
+    public void createWatchChangesEndpoint() throws Exception {
+        JiraComponent component = new JiraComponent(context);
+        component.start();
+        String query = Joiner.on("&").join(
+                concat(JIRA_URL, JIRA_URL_VALUE),
+                concat(USERNAME, USERNAME_VALUE),
+                concat(PASSWORD, PASSWORD_VALUE),
+                concat(SEND_ONLY_CHANGED_FIELD, SEND_ONLY_CHANGED_FIELD_VALUE 
+ ""),
+                concat(WATCHED_FIELDS, WATCHED_FIELDS_VALUE));
+        JiraEndpoint endpoint = (JiraEndpoint) 
component.createEndpoint("jira://watchUpdates?" + query);
+
+        assertEquals("watchupdates", endpoint.getType().name().toLowerCase());
+        assertEquals(WATCHED_FIELDS_VALUE, endpoint.getWatchedFields());
+        assertEquals(SEND_ONLY_CHANGED_FIELD_VALUE, 
endpoint.isSendOnlyUpdatedField());
+    }
+
     private String concat(String key, String val) {
         return key + "=" + val;
     }
diff --git 
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/JiraTestConstants.java
 
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/JiraTestConstants.java
index 413b6a9..4e4ee4c 100644
--- 
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/JiraTestConstants.java
+++ 
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/JiraTestConstants.java
@@ -24,4 +24,5 @@ public interface JiraTestConstants {
     String USERNAME = "someguy";
     String PASSWORD = "my_password";
     String JIRA_CREDENTIALS = TEST_JIRA_URL + "&username=" + USERNAME + 
"&password=" + PASSWORD;
+    String WATCHED_COMPONENTS = "Priority,Status,Resolution";
 }
diff --git 
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/Utils.java
 
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/Utils.java
index c7d4718..4352f84 100644
--- 
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/Utils.java
+++ 
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/Utils.java
@@ -24,6 +24,7 @@ import java.util.Map;
 
 import javax.annotation.Nullable;
 
+import com.atlassian.jira.rest.client.api.StatusCategory;
 import com.atlassian.jira.rest.client.api.domain.Attachment;
 import com.atlassian.jira.rest.client.api.domain.BasicComponent;
 import com.atlassian.jira.rest.client.api.domain.BasicPriority;
@@ -33,6 +34,7 @@ import com.atlassian.jira.rest.client.api.domain.Issue;
 import com.atlassian.jira.rest.client.api.domain.IssueLink;
 import com.atlassian.jira.rest.client.api.domain.IssueLinkType;
 import com.atlassian.jira.rest.client.api.domain.IssueType;
+import com.atlassian.jira.rest.client.api.domain.Priority;
 import com.atlassian.jira.rest.client.api.domain.Resolution;
 import com.atlassian.jira.rest.client.api.domain.Status;
 import com.atlassian.jira.rest.client.api.domain.User;
@@ -83,6 +85,23 @@ public final class Utils {
                 null, null, null, null, null);
     }
 
+    public static Issue setPriority(Issue issue, Priority p) {
+        return new Issue(
+                issue.getSummary(), issue.getSelf(), issue.getKey(), 
issue.getId(), null, issue.getIssueType(),
+                issue.getStatus(), issue.getDescription(), p, 
issue.getResolution(), null, null,
+                issue.getAssignee(), null, null, null, null, null, null, null, 
null, null, null, null, null, null, null,
+                null, null, null, null, null);
+    }
+
+    public static Issue transitionIssueDone(Issue issue) {
+        URI doneStatusUri = URI.create(TEST_JIRA_URL + "/rest/api/2/status/1");
+        URI doneResolutionUri = URI.create(TEST_JIRA_URL + 
"/rest/api/2/resolution/1");
+        StatusCategory sc = new StatusCategory(doneResolutionUri, 
"statusCategory", 1L, "SC-1", "GREEN");
+        Status status = new Status(doneStatusUri, 1L, "Done", "Done", null, 
sc);
+        Resolution resolution = new Resolution(doneResolutionUri, 5L, 
"Resolution", "Resolution");
+        return transitionIssueDone(issue, status, resolution);
+    }
+
     public static Issue createIssueWithAttachment(
             long id, String summary, String key, IssueType issueType, String 
description,
             BasicPriority priority, User assignee, Collection<Attachment> 
attachments) {
diff --git 
a/components/camel-jira/src/test/java/org/apache/camel/component/jira/consumer/WatchUpdatesConsumerTest.java
 
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/consumer/WatchUpdatesConsumerTest.java
new file mode 100644
index 0000000..01c019a
--- /dev/null
+++ 
b/components/camel-jira/src/test/java/org/apache/camel/component/jira/consumer/WatchUpdatesConsumerTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.jira.consumer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.atlassian.jira.rest.client.api.JiraRestClient;
+import com.atlassian.jira.rest.client.api.JiraRestClientFactory;
+import com.atlassian.jira.rest.client.api.SearchRestClient;
+import com.atlassian.jira.rest.client.api.domain.Issue;
+import com.atlassian.jira.rest.client.api.domain.Priority;
+import com.atlassian.jira.rest.client.api.domain.SearchResult;
+import io.atlassian.util.concurrent.Promise;
+import io.atlassian.util.concurrent.Promises;
+import org.apache.camel.CamelContext;
+import org.apache.camel.EndpointInject;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.jira.JiraComponent;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.Registry;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.apache.camel.component.jira.JiraConstants.JIRA;
+import static 
org.apache.camel.component.jira.JiraConstants.JIRA_REST_CLIENT_FACTORY;
+import static org.apache.camel.component.jira.JiraTestConstants.*;
+import static org.apache.camel.component.jira.Utils.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class WatchUpdatesConsumerTest extends CamelTestSupport {
+    private static List<Issue> issues = new ArrayList<>();
+
+    @Mock
+    private JiraRestClient jiraClient;
+
+    @Mock
+    private JiraRestClientFactory jiraRestClientFactory;
+
+    @Mock
+    private SearchRestClient searchRestClient;
+
+    @EndpointInject("mock:result")
+    private MockEndpoint mockResult;
+
+    @Override
+    protected void bindToRegistry(Registry registry) {
+        registry.bind(JIRA_REST_CLIENT_FACTORY, jiraRestClientFactory);
+    }
+
+    @BeforeAll
+    public static void beforeAll() {
+        issues.clear();
+        issues.add(createIssue(1L));
+        issues.add(createIssue(2L));
+        issues.add(createIssue(3L));
+    }
+
+    public void setMocks() {
+        SearchResult result = new SearchResult(0, 50, 100, issues);
+        Promise<SearchResult> promiseSearchResult = Promises.promise(result);
+
+        when(jiraClient.getSearchClient()).thenReturn(searchRestClient);
+        when(jiraRestClientFactory.createWithBasicHttpAuthentication(any(), 
any(), any())).thenReturn(jiraClient);
+        when(searchRestClient.searchJql(any(), any(), any(), 
any())).thenReturn(promiseSearchResult);
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        setMocks();
+        CamelContext camelContext = super.createCamelContext();
+        camelContext.disableJMX();
+        JiraComponent component = new JiraComponent(camelContext);
+        camelContext.addComponent(JIRA, component);
+        return camelContext;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("jira://watchUpdates?jiraUrl=" + JIRA_CREDENTIALS
+                     + "&jql=project=" + PROJECT + 
"&delay=5000&watchedFields=" + WATCHED_COMPONENTS)
+                             .to(mockResult);
+            }
+        };
+    }
+
+    @Test
+    public void emptyAtStartupTest() throws Exception {
+        mockResult.expectedMessageCount(0);
+        mockResult.assertIsSatisfied();
+    }
+
+    @Test
+    public void singleChangeTest() throws Exception {
+        Issue issue = setPriority(issues.get(0), new Priority(
+                null, 4L, "High", null, null, null));
+        reset(searchRestClient);
+        AtomicBoolean searched = new AtomicBoolean(false);
+        when(searchRestClient.searchJql(any(), any(), any(), 
any())).then(invocation -> {
+
+            if (!searched.get()) {
+                issues.remove(0);
+                issues.add(0, issue);
+            }
+            SearchResult result = new SearchResult(0, 50, 100, issues);
+            return Promises.promise(result);
+        });
+
+        mockResult.expectedBodiesReceived(issue.getPriority());
+        mockResult.expectedHeaderReceived("changed", "Priority");
+        mockResult.expectedHeaderReceived("issueKey", "TST-1");
+        mockResult.expectedMessageCount(1);
+        mockResult.assertIsSatisfied(0);
+    }
+
+    @Test
+    public void multipleChangesWithAddedNewIssueTest() throws Exception {
+        final Issue issue = transitionIssueDone(issues.get(1));
+        final Issue issue2 = setPriority(issues.get(2), new Priority(
+                null, 4L, "High", null, null, null));
+
+        reset(searchRestClient);
+        AtomicBoolean searched = new AtomicBoolean(false);
+        when(searchRestClient.searchJql(any(), any(), any(), 
any())).then(invocation -> {
+            if (!searched.get()) {
+                issues.add(createIssue(4L));
+                issues.remove(1);
+                issues.add(1, issue);
+                issues.remove(2);
+                issues.add(2, issue2);
+                searched.set(true);
+            }
+
+            SearchResult result = new SearchResult(0, 50, 3, issues);
+            return Promises.promise(result);
+        });
+
+        mockResult.expectedMessageCount(3);
+        mockResult.expectedBodiesReceivedInAnyOrder(issue.getStatus(), 
issue.getResolution(), issue2.getPriority());
+        mockResult.assertIsSatisfied(1000);
+    }
+
+}
diff --git 
a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
 
b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
index 1eed630..dbc0cc1 100644
--- 
a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
+++ 
b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
@@ -8262,8 +8262,8 @@ public class StaticEndpointBuilders {
      * AddIssue, AttachFile, DeleteIssue, TransitionIssue, UpdateIssue,
      * Watchers. See this class javadoc description for more information.
      * The value can be one of: ADDCOMMENT, ADDISSUE, ATTACH, DELETEISSUE,
-     * NEWISSUES, NEWCOMMENTS, UPDATEISSUE, TRANSITIONISSUE, WATCHERS,
-     * ADDISSUELINK, ADDWORKLOG, FETCHISSUE, FETCHCOMMENTS
+     * NEWISSUES, NEWCOMMENTS, WATCHUPDATES, UPDATEISSUE, TRANSITIONISSUE,
+     * WATCHERS, ADDISSUELINK, ADDWORKLOG, FETCHISSUE, FETCHCOMMENTS
      * 
      * @param path type
      */
@@ -8286,8 +8286,8 @@ public class StaticEndpointBuilders {
      * AddIssue, AttachFile, DeleteIssue, TransitionIssue, UpdateIssue,
      * Watchers. See this class javadoc description for more information.
      * The value can be one of: ADDCOMMENT, ADDISSUE, ATTACH, DELETEISSUE,
-     * NEWISSUES, NEWCOMMENTS, UPDATEISSUE, TRANSITIONISSUE, WATCHERS,
-     * ADDISSUELINK, ADDWORKLOG, FETCHISSUE, FETCHCOMMENTS
+     * NEWISSUES, NEWCOMMENTS, WATCHUPDATES, UPDATEISSUE, TRANSITIONISSUE,
+     * WATCHERS, ADDISSUELINK, ADDWORKLOG, FETCHISSUE, FETCHCOMMENTS
      * 
      * @param componentName to use a custom component name for the endpoint
      * instead of the default name
diff --git 
a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/JiraEndpointBuilderFactory.java
 
b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/JiraEndpointBuilderFactory.java
index 2b052dd..e5352fb 100644
--- 
a/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/JiraEndpointBuilderFactory.java
+++ 
b/core/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/JiraEndpointBuilderFactory.java
@@ -157,6 +157,47 @@ public interface JiraEndpointBuilderFactory {
             return this;
         }
         /**
+         * Indicator for sending only changed fields in exchange body or issue
+         * object. By default consumer sends only changed fields.
+         * 
+         * The option is a: <code>boolean</code> type.
+         * 
+         * Default: true
+         * Group: consumer
+         */
+        default JiraEndpointConsumerBuilder sendOnlyUpdatedField(
+                boolean sendOnlyUpdatedField) {
+            doSetProperty("sendOnlyUpdatedField", sendOnlyUpdatedField);
+            return this;
+        }
+        /**
+         * Indicator for sending only changed fields in exchange body or issue
+         * object. By default consumer sends only changed fields.
+         * 
+         * The option will be converted to a <code>boolean</code> type.
+         * 
+         * Default: true
+         * Group: consumer
+         */
+        default JiraEndpointConsumerBuilder sendOnlyUpdatedField(
+                String sendOnlyUpdatedField) {
+            doSetProperty("sendOnlyUpdatedField", sendOnlyUpdatedField);
+            return this;
+        }
+        /**
+         * Comma separated list of fields to watch for changes. Status,Priority
+         * are the defaults.
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Default: Status,Priority
+         * Group: consumer
+         */
+        default JiraEndpointConsumerBuilder watchedFields(String 
watchedFields) {
+            doSetProperty("watchedFields", watchedFields);
+            return this;
+        }
+        /**
          * (OAuth only) The access token generated by the Jira server.
          * 
          * The option is a: <code>java.lang.String</code> type.
@@ -788,8 +829,8 @@ public interface JiraEndpointBuilderFactory {
          * AddIssue, AttachFile, DeleteIssue, TransitionIssue, UpdateIssue,
          * Watchers. See this class javadoc description for more information.
          * The value can be one of: ADDCOMMENT, ADDISSUE, ATTACH, DELETEISSUE,
-         * NEWISSUES, NEWCOMMENTS, UPDATEISSUE, TRANSITIONISSUE, WATCHERS,
-         * ADDISSUELINK, ADDWORKLOG, FETCHISSUE, FETCHCOMMENTS
+         * NEWISSUES, NEWCOMMENTS, WATCHUPDATES, UPDATEISSUE, TRANSITIONISSUE,
+         * WATCHERS, ADDISSUELINK, ADDWORKLOG, FETCHISSUE, FETCHCOMMENTS
          * 
          * @param path type
          */
@@ -811,8 +852,8 @@ public interface JiraEndpointBuilderFactory {
          * AddIssue, AttachFile, DeleteIssue, TransitionIssue, UpdateIssue,
          * Watchers. See this class javadoc description for more information.
          * The value can be one of: ADDCOMMENT, ADDISSUE, ATTACH, DELETEISSUE,
-         * NEWISSUES, NEWCOMMENTS, UPDATEISSUE, TRANSITIONISSUE, WATCHERS,
-         * ADDISSUELINK, ADDWORKLOG, FETCHISSUE, FETCHCOMMENTS
+         * NEWISSUES, NEWCOMMENTS, WATCHUPDATES, UPDATEISSUE, TRANSITIONISSUE,
+         * WATCHERS, ADDISSUELINK, ADDWORKLOG, FETCHISSUE, FETCHCOMMENTS
          * 
          * @param componentName to use a custom component name for the endpoint
          * instead of the default name

Reply via email to