This is an automated email from the ASF dual-hosted git repository.
fmariani pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 8feb64214ca Update Camel application with openrewrite via camel jbang
8feb64214ca is described below
commit 8feb64214ca1591a541eee65b94171e3ebbb8ae8
Author: Croway <[email protected]>
AuthorDate: Tue Jan 21 14:55:29 2025 +0100
Update Camel application with openrewrite via camel jbang
Handle quarkus
Handle extra repos
Handle downloads concurrently
Refactor camel update
Define extra recipes and artifacts
Use cache + List test
Add documentation
---
.../org/apache/camel/catalog/components/kafka.json | 4 +-
.../org/apache/camel/component/kafka/kafka.json | 4 +-
.../camel/component/kafka/KafkaConfiguration.java | 3 +-
.../modules/ROOT/pages/camel-jbang.adoc | 83 +++++-
.../dsl/KafkaComponentBuilderFactory.java | 1 -
.../endpoint/dsl/KafkaEndpointBuilderFactory.java | 2 -
.../dsl/jbang/core/commands/CamelJBangMain.java | 6 +
.../core/commands/update/CamelQuarkusUpdate.java | 95 +++++++
.../jbang/core/commands/update/CamelUpdate.java | 169 ++++++++++++
.../core/commands/update/CamelUpdateException.java | 32 +++
.../core/commands/update/CamelUpdateMixin.java | 91 +++++++
.../dsl/jbang/core/commands/update/Update.java | 55 ++++
.../jbang/core/commands/update/UpdateCommand.java | 37 +++
.../dsl/jbang/core/commands/update/UpdateList.java | 299 +++++++++++++++++++++
.../dsl/jbang/core/commands/update/UpdateRun.java | 136 ++++++++++
.../jbang/core/commands/update/UpdateListTest.java | 39 +++
16 files changed, 1046 insertions(+), 10 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kafka.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kafka.json
index e01c39e68ee..81a556f89f7 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kafka.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kafka.json
@@ -37,7 +37,7 @@
"autoCommitEnable": { "index": 10, "kind": "property", "displayName":
"Auto Commit Enable", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true,
"configurationClass": "org.apache.camel.component.kafka.KafkaConfiguration",
"configurationField": "configuration", "description": "If true, periodically
commit to ZooKeeper the offset of messages already fetched [...]
"autoCommitIntervalMs": { "index": 11, "kind": "property", "displayName":
"Auto Commit Interval Ms", "group": "consumer", "label": "consumer",
"required": false, "type": "integer", "javaType": "java.lang.Integer",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"5000", "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "The frequency in ms that the consumer offsets
are committed [...]
"autoOffsetReset": { "index": 12, "kind": "property", "displayName": "Auto
Offset Reset", "group": "consumer", "label": "consumer", "required": false,
"type": "string", "javaType": "java.lang.String", "enum": [ "latest",
"earliest", "none" ], "deprecated": false, "autowired": false, "secret": false,
"defaultValue": "latest", "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "What to do when there is no i [...]
- "batching": { "index": 13, "kind": "property", "displayName": "Batching",
"group": "consumer", "label": "consumer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "Whether to use batching for processing or
streaming. The default is false, which uses streaming. I [...]
+ "batching": { "index": 13, "kind": "property", "displayName": "Batching",
"group": "consumer", "label": "consumer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "Whether to use batching for processing or
streaming. The default is false, which uses streaming. I [...]
"batchingIntervalMs": { "index": 14, "kind": "property", "displayName":
"Batching Interval Ms", "group": "consumer", "label": "consumer", "required":
false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "In consumer batching mode, then this option is
specifying a time in millis, to trigger ba [...]
"breakOnFirstError": { "index": 15, "kind": "property", "displayName":
"Break On First Error", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass": "org.apache.camel.component.kafka.KafkaConfiguration",
"configurationField": "configuration", "description": "This options controls
what happens when a consumer is processing an exchange [...]
"bridgeErrorHandler": { "index": 16, "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 (if possible) occurred while the Camel consumer is trying
to pickup incoming messages, or the lik [...]
@@ -171,7 +171,7 @@
"autoCommitEnable": { "index": 10, "kind": "parameter", "displayName":
"Auto Commit Enable", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true,
"configurationClass": "org.apache.camel.component.kafka.KafkaConfiguration",
"configurationField": "configuration", "description": "If true, periodically
commit to ZooKeeper the offset of messages already fetched [...]
"autoCommitIntervalMs": { "index": 11, "kind": "parameter", "displayName":
"Auto Commit Interval Ms", "group": "consumer", "label": "consumer",
"required": false, "type": "integer", "javaType": "java.lang.Integer",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"5000", "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "The frequency in ms that the consumer offsets
are committed [...]
"autoOffsetReset": { "index": 12, "kind": "parameter", "displayName":
"Auto Offset Reset", "group": "consumer", "label": "consumer", "required":
false, "type": "string", "javaType": "java.lang.String", "enum": [ "latest",
"earliest", "none" ], "deprecated": false, "autowired": false, "secret": false,
"defaultValue": "latest", "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "What to do when there is no [...]
- "batching": { "index": 13, "kind": "parameter", "displayName": "Batching",
"group": "consumer", "label": "consumer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "Whether to use batching for processing or
streaming. The default is false, which uses streaming. [...]
+ "batching": { "index": 13, "kind": "parameter", "displayName": "Batching",
"group": "consumer", "label": "consumer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "Whether to use batching for processing or
streaming. The default is false, which uses streaming. [...]
"batchingIntervalMs": { "index": 14, "kind": "parameter", "displayName":
"Batching Interval Ms", "group": "consumer", "label": "consumer", "required":
false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "In consumer batching mode, then this option is
specifying a time in millis, to trigger b [...]
"breakOnFirstError": { "index": 15, "kind": "parameter", "displayName":
"Break On First Error", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass": "org.apache.camel.component.kafka.KafkaConfiguration",
"configurationField": "configuration", "description": "This options controls
what happens when a consumer is processing an exchange [...]
"checkCrcs": { "index": 16, "kind": "parameter", "displayName": "Check
Crcs", "group": "consumer", "label": "consumer", "required": false, "type":
"boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "true", "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "Automatically check the CRC32 of the records
consumed. This ensures no on-the-wire [...]
diff --git
a/components/camel-kafka/src/generated/resources/META-INF/org/apache/camel/component/kafka/kafka.json
b/components/camel-kafka/src/generated/resources/META-INF/org/apache/camel/component/kafka/kafka.json
index e01c39e68ee..81a556f89f7 100644
---
a/components/camel-kafka/src/generated/resources/META-INF/org/apache/camel/component/kafka/kafka.json
+++
b/components/camel-kafka/src/generated/resources/META-INF/org/apache/camel/component/kafka/kafka.json
@@ -37,7 +37,7 @@
"autoCommitEnable": { "index": 10, "kind": "property", "displayName":
"Auto Commit Enable", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true,
"configurationClass": "org.apache.camel.component.kafka.KafkaConfiguration",
"configurationField": "configuration", "description": "If true, periodically
commit to ZooKeeper the offset of messages already fetched [...]
"autoCommitIntervalMs": { "index": 11, "kind": "property", "displayName":
"Auto Commit Interval Ms", "group": "consumer", "label": "consumer",
"required": false, "type": "integer", "javaType": "java.lang.Integer",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"5000", "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "The frequency in ms that the consumer offsets
are committed [...]
"autoOffsetReset": { "index": 12, "kind": "property", "displayName": "Auto
Offset Reset", "group": "consumer", "label": "consumer", "required": false,
"type": "string", "javaType": "java.lang.String", "enum": [ "latest",
"earliest", "none" ], "deprecated": false, "autowired": false, "secret": false,
"defaultValue": "latest", "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "What to do when there is no i [...]
- "batching": { "index": 13, "kind": "property", "displayName": "Batching",
"group": "consumer", "label": "consumer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "Whether to use batching for processing or
streaming. The default is false, which uses streaming. I [...]
+ "batching": { "index": 13, "kind": "property", "displayName": "Batching",
"group": "consumer", "label": "consumer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "Whether to use batching for processing or
streaming. The default is false, which uses streaming. I [...]
"batchingIntervalMs": { "index": 14, "kind": "property", "displayName":
"Batching Interval Ms", "group": "consumer", "label": "consumer", "required":
false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "In consumer batching mode, then this option is
specifying a time in millis, to trigger ba [...]
"breakOnFirstError": { "index": 15, "kind": "property", "displayName":
"Break On First Error", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass": "org.apache.camel.component.kafka.KafkaConfiguration",
"configurationField": "configuration", "description": "This options controls
what happens when a consumer is processing an exchange [...]
"bridgeErrorHandler": { "index": 16, "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 (if possible) occurred while the Camel consumer is trying
to pickup incoming messages, or the lik [...]
@@ -171,7 +171,7 @@
"autoCommitEnable": { "index": 10, "kind": "parameter", "displayName":
"Auto Commit Enable", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true,
"configurationClass": "org.apache.camel.component.kafka.KafkaConfiguration",
"configurationField": "configuration", "description": "If true, periodically
commit to ZooKeeper the offset of messages already fetched [...]
"autoCommitIntervalMs": { "index": 11, "kind": "parameter", "displayName":
"Auto Commit Interval Ms", "group": "consumer", "label": "consumer",
"required": false, "type": "integer", "javaType": "java.lang.Integer",
"deprecated": false, "autowired": false, "secret": false, "defaultValue":
"5000", "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "The frequency in ms that the consumer offsets
are committed [...]
"autoOffsetReset": { "index": 12, "kind": "parameter", "displayName":
"Auto Offset Reset", "group": "consumer", "label": "consumer", "required":
false, "type": "string", "javaType": "java.lang.String", "enum": [ "latest",
"earliest", "none" ], "deprecated": false, "autowired": false, "secret": false,
"defaultValue": "latest", "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "What to do when there is no [...]
- "batching": { "index": 13, "kind": "parameter", "displayName": "Batching",
"group": "consumer", "label": "consumer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "Whether to use batching for processing or
streaming. The default is false, which uses streaming. [...]
+ "batching": { "index": 13, "kind": "parameter", "displayName": "Batching",
"group": "consumer", "label": "consumer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "Whether to use batching for processing or
streaming. The default is false, which uses streaming. [...]
"batchingIntervalMs": { "index": 14, "kind": "parameter", "displayName":
"Batching Interval Ms", "group": "consumer", "label": "consumer", "required":
false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "In consumer batching mode, then this option is
specifying a time in millis, to trigger b [...]
"breakOnFirstError": { "index": 15, "kind": "parameter", "displayName":
"Break On First Error", "group": "consumer", "label": "consumer", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": false,
"configurationClass": "org.apache.camel.component.kafka.KafkaConfiguration",
"configurationField": "configuration", "description": "This options controls
what happens when a consumer is processing an exchange [...]
"checkCrcs": { "index": 16, "kind": "parameter", "displayName": "Check
Crcs", "group": "consumer", "label": "consumer", "required": false, "type":
"boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired":
false, "secret": false, "defaultValue": "true", "configurationClass":
"org.apache.camel.component.kafka.KafkaConfiguration", "configurationField":
"configuration", "description": "Automatically check the CRC32 of the records
consumed. This ensures no on-the-wire [...]
diff --git
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConfiguration.java
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConfiguration.java
index 32ee8aef9d0..0ee358a5689 100755
---
a/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConfiguration.java
+++
b/components/camel-kafka/src/main/java/org/apache/camel/component/kafka/KafkaConfiguration.java
@@ -2003,8 +2003,7 @@ public class KafkaConfiguration implements Cloneable,
HeaderFilterStrategyAware
* In streaming mode, then a single kafka record is processed per Camel
exchange in the message body.
*
* In batching mode, then Camel groups many kafka records together as a
List<Exchange> objects in the message body.
- * The option maxPollRecords is used to define the number of records to
group together in batching mode. See also
- * the batchingIntervalMs option.
+ * The option maxPollRecords is used to define the number of records to
group together in batching mode.
*/
public void setBatching(boolean batching) {
this.batching = batching;
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
index 1c81b8c0371..98020c933cc 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
@@ -4110,4 +4110,85 @@ $ camel infra log service
[service] ...
[service] ...
[service] ...
----
\ No newline at end of file
+---
+
+== Update
+
+Apache Camel applications can be automatically updated using Camel JBang. The
update command provides two main operations:
+
+- `list`: Shows available Apache Camel versions for updating
+- `run`: Executes the actual update process
+
+The update process leverages the
https://github.com/apache/camel-upgrade-recipes[Apache Camel Open Rewrite
recipes] and supports three application types:
+
+- Plain Camel (camel-main)
+- Camel Quarkus
+- Camel Spring Boot
+
+While Camel and Camel Spring Boot updates primarily use camel-upgrade-recipes,
+Camel Quarkus updates involve both the Quarkus runtime (via
https://github.com/openrewrite/rewrite-quarkus[Rewrite Quarkus]) and Apache
Camel recipes.
+
+=== Listing Available Updates
+
+To see which versions are available for updating, use:
+
+[source,bash]
+----
+$ camel update list
+
+ VERSION RUNTIME RUNTIME VERSION DESCRIPTION
+ 4.4.4 Camel Quarkus 3.8.x Migrates
`camel 4.0` quarkus application to `camel 4.4`.
+ 4.8.0 Camel Migrates
Apache Camel 4 application to Apache Camel 4.8.0
+ 4.8.3 Camel Quarkus 3.15.x Migrates
`camel 4.4` quarkus application to `camel 4.8`.
+ 4.9.0 Camel Migrates
Apache Camel 4 application to Apache Camel 4.9.0
+ 4.9.0 Camel Spring Boot 3.4.0 Migrates
Apache Camel Spring Boot 4 application to Apache Camel Spring Boot 4.9.0
+----
+
+=== Running Updates
+
+To perform an update to a specific version:
+
+[source,bash]
+----
+$ camel update run $VERSION
+----
+
+NOTE: The update commands must be executed in the project directory containing
the pom.xml file.
+
+==== Configuration Options
+
+The update process can be customized with several options:
+`--runtime`: Specifies the application type:
+
+- `camel-main` - Plain Camel applications
+- `spring-boot` - Camel Spring Boot applications
+- `quarkus` - Camel Quarkus applications
+
+`--repos`: Additional Maven repositories to use during the update
+`--dry-run`: Preview the changes without applying them
+`--extraActiveRecipes`: Comma-separated list of additional recipe names to
apply
+`--extraRecipeArtifactCoordinates`: Comma-separated list of Maven coordinates
for extra recipes (format: groupId:artifactId:version)
+Use `--help` to see all available options.
+
+==== Examples
+
+Update a Camel Quarkus application
+
+[source,bash]
+----
+$ camel update run 4.8.3 --runtime=quarkus --dryRun
+----
+
+Update a plain Camel application
+
+[source,bash]
+----
+$ camel update run 4.9.0 --runtime=camel-main --repos=https://myMaven/repo
--extraActiveRecipes=my.first.Recipe,my.second.Recipe
--extraRecipeArtifactCoordinates=ex.my.org:recipes:1.0.0
+----
+
+Update a Spring Boot application with and extra Spring Boot 3.3 upgrade recipe
+
+[source,bash]
+----
+$ camel update run 4.9.0 --runtime=spring-boot
--extraActiveRecipes=org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_3
--extraRecipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:6.0.2
+----
\ No newline at end of file
diff --git
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/KafkaComponentBuilderFactory.java
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/KafkaComponentBuilderFactory.java
index 44160b56f69..8c51614c7fd 100644
---
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/KafkaComponentBuilderFactory.java
+++
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/KafkaComponentBuilderFactory.java
@@ -313,7 +313,6 @@ public interface KafkaComponentBuilderFactory {
* batching mode, then Camel groups many kafka records together as a
* List objects in the message body. The option maxPollRecords is used
* to define the number of records to group together in batching mode.
- * See also the batchingIntervalMs option.
*
* The option is a: <code>boolean</code> type.
*
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KafkaEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KafkaEndpointBuilderFactory.java
index 5b1dec8dcab..1f47c78e78f 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KafkaEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KafkaEndpointBuilderFactory.java
@@ -444,7 +444,6 @@ public interface KafkaEndpointBuilderFactory {
* batching mode, then Camel groups many kafka records together as a
* List objects in the message body. The option maxPollRecords is used
* to define the number of records to group together in batching mode.
- * See also the batchingIntervalMs option.
*
* The option is a: <code>boolean</code> type.
*
@@ -465,7 +464,6 @@ public interface KafkaEndpointBuilderFactory {
* batching mode, then Camel groups many kafka records together as a
* List objects in the message body. The option maxPollRecords is used
* to define the number of records to group together in batching mode.
- * See also the batchingIntervalMs option.
*
* The option will be converted to a <code>boolean</code> type.
*
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
index f127b1d052a..d168a0b66f7 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
@@ -49,6 +49,9 @@ import
org.apache.camel.dsl.jbang.core.commands.plugin.PluginCommand;
import org.apache.camel.dsl.jbang.core.commands.plugin.PluginDelete;
import org.apache.camel.dsl.jbang.core.commands.plugin.PluginGet;
import org.apache.camel.dsl.jbang.core.commands.process.*;
+import org.apache.camel.dsl.jbang.core.commands.update.UpdateCommand;
+import org.apache.camel.dsl.jbang.core.commands.update.UpdateList;
+import org.apache.camel.dsl.jbang.core.commands.update.UpdateRun;
import org.apache.camel.dsl.jbang.core.commands.version.VersionCommand;
import org.apache.camel.dsl.jbang.core.commands.version.VersionGet;
import org.apache.camel.dsl.jbang.core.commands.version.VersionList;
@@ -175,6 +178,9 @@ public class CamelJBangMain implements Callable<Integer> {
.addSubcommand("get", new CommandLine(new
InfraGet(main)))
.addSubcommand("ps", new CommandLine(new
InfraPs(main)))
.addSubcommand("log", new CommandLine(new
InfraLog(main))))
+ .addSubcommand("update", new CommandLine(new
UpdateCommand(main))
+ .addSubcommand("list", new CommandLine(new
UpdateList(main)))
+ .addSubcommand("run", new CommandLine(new
UpdateRun(main))))
.setParameterExceptionHandler(new
MissingPluginParameterExceptionHandler());
PluginHelper.addPlugins(commandLine, main, args);
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelQuarkusUpdate.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelQuarkusUpdate.java
new file mode 100644
index 00000000000..d587dfa3840
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelQuarkusUpdate.java
@@ -0,0 +1,95 @@
+/*
+ * 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.dsl.jbang.core.commands.update;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.main.download.MavenDependencyDownloader;
+
+public final class CamelQuarkusUpdate implements Update {
+
+ private List<String> commands = new ArrayList<>();
+ private CamelUpdateMixin updateMixin;
+ private MavenDependencyDownloader downloader;
+
+ private final String QUARKUS_UPDATE_ARTIFACTID = "camel-quarkus-catalog";
+
+ public CamelQuarkusUpdate(CamelUpdateMixin updateMixin,
MavenDependencyDownloader downloader) {
+ this.updateMixin = updateMixin;
+ this.downloader = downloader;
+ }
+
+ /**
+ * Quarkus updates are in the form 3.8, 3.15, 3.17... Downloads Camel
Quarkus catalog for the given Camel version
+ * and get the Quarkus stream version
+ *
+ * @return
+ */
+ public String getQuarkusStream() {
+ // Assume that the quarkus updates are in the form 3.8, 3.15, 3.16...
+ List<String[]> qVersions
+ =
downloader.resolveAvailableVersions("org.apache.camel.quarkus",
QUARKUS_UPDATE_ARTIFACTID,
+ updateMixin.version,
+ updateMixin.repos);
+ String streamVersion = null;
+ for (String[] qVersion : qVersions) {
+ if (qVersion[0].equals(updateMixin.version)) {
+ streamVersion = qVersion[1].substring(0,
qVersion[1].lastIndexOf('.'));
+ }
+ }
+
+ return streamVersion;
+ }
+
+ @Override
+ public String debug() {
+ String result = "--no-transfer-progress";
+ if (updateMixin.debug) {
+ result = "-X";
+ }
+
+ return result;
+ }
+
+ @Override
+ public String runMode() {
+ String result = "-DrewriteFullRun";
+ if (updateMixin.dryRun) {
+ result = "-DrewriteDryRun";
+ }
+
+ return result;
+ }
+
+ @Override
+ public List<String> command() throws CamelUpdateException {
+ commands.add(mvnProgramCall());
+ commands.add(debug());
+ commands.add(String.format("%s:quarkus-maven-plugin:%s:update",
updateMixin.quarkusMavenPluginGroupId,
+ updateMixin.quarkusMavenPluginVersion));
+ commands.add("-Dstream=" + getQuarkusStream());
+ commands.add(runMode());
+
+ return commands;
+ }
+
+ @Override
+ public String getArtifactCoordinates() {
+ return QUARKUS_UPDATE_ARTIFACTID;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdate.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdate.java
new file mode 100644
index 00000000000..2469a55e2fd
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdate.java
@@ -0,0 +1,169 @@
+/*
+ * 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.dsl.jbang.core.commands.update;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.common.RuntimeType;
+import org.apache.camel.main.download.MavenDependencyDownloader;
+import org.apache.camel.tooling.maven.MavenArtifact;
+
+public final class CamelUpdate implements Update {
+
+ private List<String> commands = new ArrayList<>();
+ private CamelUpdateMixin updateMixin;
+ private MavenDependencyDownloader downloader;
+
+ public CamelUpdate(CamelUpdateMixin updateMixin, MavenDependencyDownloader
downloader) {
+ this.updateMixin = updateMixin;
+ this.downloader = downloader;
+ }
+
+ /**
+ * Download the jar containing the recipes and extract the recipe name to
be used in the maven command
+ *
+ * @return
+ */
+ public List<String> activeRecipes() throws CamelUpdateException {
+ List<Recipe> recipes;
+ MavenArtifact mavenArtifact
+ = downloader.downloadArtifact("org.apache.camel.upgrade",
getArtifactCoordinates(), updateMixin.version);
+
+ try {
+ recipes = getRecipesInJar(mavenArtifact.getFile());
+ } catch (IOException ex) {
+ throw new CamelUpdateException(ex);
+ }
+
+ List<String> activeRecipes = new ArrayList<>();
+ for (Recipe recipe : recipes) {
+ // The recipe named latest.yaml contains all the recipe for the
update up to the selected version
+ if (recipe.name().contains("latest")) {
+ activeRecipes.clear();
+ recipe.recipeName().ifPresent(name -> activeRecipes.add(name));
+ break;
+ }
+
+ recipe.recipeName().ifPresent(name -> activeRecipes.add(name));
+ }
+
+ return activeRecipes;
+ }
+
+ private List<Recipe> getRecipesInJar(File jar) throws IOException {
+ List<Recipe> recipes = new ArrayList<>();
+ Path jarPath = jar.toPath();
+
+ try (FileSystem fileSystem = FileSystems.newFileSystem(jarPath,
(ClassLoader) null)) {
+ Path recipePath = fileSystem.getPath("META-INF", "rewrite");
+ if (Files.exists(recipePath)) {
+ try (Stream<Path> walk = Files.walk(recipePath)) {
+ walk.filter(p -> p.toString().endsWith(".yaml"))
+ .forEach(p -> {
+ try {
+ recipes.add(new Recipe(
+ p.getFileName().toString(),
+ Files.readString(p)));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ });
+ }
+ }
+ }
+ return recipes;
+ }
+
+ @Override
+ public String debug() {
+ String result = "--no-transfer-progress";
+ if (updateMixin.debug) {
+ result = "-X";
+ }
+
+ return result;
+ }
+
+ @Override
+ public String runMode() {
+ String task = "run";
+ if (updateMixin.dryRun) {
+ task = "dryRun";
+ }
+
+ return task;
+ }
+
+ @Override
+ public List<String> command() throws CamelUpdateException {
+ commands.add(mvnProgramCall());
+ commands.add(debug());
+ commands.add("org.openrewrite.maven:rewrite-maven-plugin:" +
updateMixin.openRewriteVersion + ":"
+ + runMode());
+
+ List<String> coordinates = new ArrayList<>();
+ coordinates.add(String.format("org.apache.camel.upgrade:%s:%s",
getArtifactCoordinates(), updateMixin.version));
+ if (updateMixin.extraRecipeArtifactCoordinates != null &&
!updateMixin.extraRecipeArtifactCoordinates.isEmpty()) {
+ coordinates.addAll(updateMixin.extraRecipeArtifactCoordinates);
+ }
+
+ commands.add("-Drewrite.recipeArtifactCoordinates=" +
+ coordinates.stream().collect(Collectors.joining(",")));
+
+ List<String> recipes = new ArrayList<>();
+ recipes.addAll(activeRecipes());
+ if (updateMixin.extraActiveRecipes != null &&
!updateMixin.extraActiveRecipes.isEmpty()) {
+ recipes.addAll(updateMixin.extraActiveRecipes);
+ }
+ commands.add("-Drewrite.activeRecipes=" + recipes
+ .stream().collect(Collectors.joining(",")));
+
+ return commands;
+ }
+
+ public String getArtifactCoordinates() {
+ return updateMixin.runtime == RuntimeType.springBoot
+ ? updateMixin.camelSpringBootArtifactCoordinates :
updateMixin.camelArtifactCoordinates;
+ }
+
+ record Recipe(String name, String content) {
+
+ /**
+ * Retrieves the name of the recipe if it is a Camel upgrade recipe.
+ *
+ * @return an Optional containing the recipe name if it is a Camel
upgrade recipe, otherwise empty
+ */
+ public Optional<String> recipeName() {
+ return Arrays.stream(content().split(System.lineSeparator()))
+ .filter(l -> l.startsWith("name") &&
l.contains("org.apache.camel.upgrade"))
+ .map(l ->
l.substring(l.indexOf("org.apache.camel.upgrade")))
+ .findFirst();
+ }
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdateException.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdateException.java
new file mode 100644
index 00000000000..2c7e5c1097e
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdateException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.dsl.jbang.core.commands.update;
+
+public class CamelUpdateException extends Exception {
+
+ public CamelUpdateException(String message) {
+ super(message);
+ }
+
+ public CamelUpdateException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public CamelUpdateException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdateMixin.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdateMixin.java
new file mode 100644
index 00000000000..500447167d3
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/CamelUpdateMixin.java
@@ -0,0 +1,91 @@
+/*
+ * 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.dsl.jbang.core.commands.update;
+
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.core.common.RuntimeCompletionCandidates;
+import org.apache.camel.dsl.jbang.core.common.RuntimeType;
+import org.apache.camel.dsl.jbang.core.common.RuntimeTypeConverter;
+import picocli.CommandLine;
+
+public class CamelUpdateMixin {
+
+ @CommandLine.Parameters(description = "The version to which the Camel
project should be updated.", arity = "1")
+ String version;
+
+ @CommandLine.Option(names = { "--openRewriteVersion" },
+ description = "The version of OpenRewrite to use
during the update process.",
+ defaultValue = "6.0.4")
+ String openRewriteVersion;
+
+ @CommandLine.Option(names = { "--camelArtifact" },
+ description = "The Maven artifact coordinates for the
Camel upgrade recipes.",
+ defaultValue = "camel-upgrade-recipes")
+ String camelArtifactCoordinates;
+
+ @CommandLine.Option(names = { "--camelSpringBootArtifact" },
+ description = "The Maven artifact coordinates for the
Camel Spring Boot upgrade recipes.",
+ defaultValue = "camel-spring-boot-upgrade-recipes")
+ String camelSpringBootArtifactCoordinates;
+
+ @CommandLine.Option(names = { "--debug" },
+ defaultValue = "false",
+ description = "Enables debug logging if set to true.")
+ boolean debug;
+
+ @CommandLine.Option(names = { "--quarkusMavenPluginVersion" },
+ description = "The version of the Quarkus Maven plugin
to use.",
+ defaultValue = RuntimeType.QUARKUS_VERSION)
+ String quarkusMavenPluginVersion;
+
+ @CommandLine.Option(names = { "--quarkusMavenPluginGroupId" },
+ description = "The group ID of the Quarkus Maven
plugin.",
+ defaultValue = "io.quarkus")
+ String quarkusMavenPluginGroupId;
+
+ @CommandLine.Option(names = { "--dryRun" },
+ description = "If set to true, performs a dry run of
the update process without making any changes.",
+ defaultValue = "false")
+ boolean dryRun;
+
+ @CommandLine.Option(names = { "--runtime" },
+ completionCandidates =
RuntimeCompletionCandidates.class,
+ defaultValue = "camel-main",
+ converter = RuntimeTypeConverter.class,
+ description = "Runtime (${COMPLETION-CANDIDATES})")
+ RuntimeType runtime = RuntimeType.main;
+
+ @CommandLine.Option(names = { "--repos" },
+ description = "Additional maven repositories for
download on-demand (Use commas to separate multiple repositories)")
+ String repos;
+
+ @CommandLine.Option(names = { "--extraActiveRecipes" },
+ description = "Comma separated list of recipes to be
executed after the Camel one, " +
+ "make sure the artifact containing the
recipes is added via extraRecipeArtifactCoordinates")
+ List<String> extraActiveRecipes;
+
+ @CommandLine.Option(names = { "--extraRecipeArtifactCoordinates" },
+ description = "Comma separated list of artifact
coordinates containing extraActiveRecipes, " +
+ "ex.my.org:recipes:1.0.0")
+ List<String> extraRecipeArtifactCoordinates;
+
+ @CommandLine.Option(names = { "--upgradeTimeout" },
+ description = "Time to wait, in seconds, before
shutting down the upgrade process",
+ defaultValue = "240")
+ int upgradeTimeout;
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/Update.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/Update.java
new file mode 100644
index 00000000000..9bd5714d230
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/Update.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dsl.jbang.core.commands.update;
+
+import java.util.List;
+
+import org.apache.camel.util.FileUtil;
+
+/**
+ * Interface defining the contract for generating the command to update Camel
artifacts. This interface provides common
+ * methods to Camel, Camel Spring Boot and Camel Quarkus applications.
+ *
+ * @see CamelUpdateException
+ */
+public sealed interface Update permits CamelUpdate, CamelQuarkusUpdate {
+
+ String debug();
+
+ String runMode();
+
+ /**
+ * Returns the command to execute that updates Apache Camel.
+ *
+ * @return a list of strings representing the command
to execute.
+ * @throws CamelUpdateException if an error occurs while generating the
command.
+ */
+ List<String> command() throws CamelUpdateException;
+
+ String getArtifactCoordinates();
+
+ default String mvnProgramCall() {
+ String mvnProgramCall;
+ if (FileUtil.isWindows()) {
+ mvnProgramCall = "cmd /c mvn";
+ } else {
+ mvnProgramCall = "mvn";
+ }
+
+ return mvnProgramCall;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateCommand.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateCommand.java
new file mode 100644
index 00000000000..c3a8752a018
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateCommand.java
@@ -0,0 +1,37 @@
+/*
+ * 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.dsl.jbang.core.commands.update;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import picocli.CommandLine;
+
[email protected](name = "update",
+ description = "Update Camel project")
+public class UpdateCommand extends CamelCommand {
+
+ public UpdateCommand(CamelJBangMain main) {
+ super(main);
+ }
+
+ @Override
+ public Integer doCall() throws Exception {
+ // defaults to list
+ new CommandLine(new UpdateList(getMain())).execute();
+ return 0;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateList.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateList.java
new file mode 100644
index 00000000000..01433e93bd0
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateList.java
@@ -0,0 +1,299 @@
+/*
+ * 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.dsl.jbang.core.commands.update;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+
+import com.github.freva.asciitable.AsciiTable;
+import com.github.freva.asciitable.Column;
+import com.github.freva.asciitable.HorizontalAlign;
+import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.main.download.MavenDependencyDownloader;
+import org.apache.camel.tooling.maven.MavenArtifact;
+import org.apache.camel.util.json.Jsoner;
+import picocli.CommandLine;
+
+/**
+ * A command-line tool for listing available update versions for Apache Camel
and its runtime variants.
+ *
+ * <p>
+ * The command supports listing versions in both human-readable ASCII table
format and JSON format. It downloads version
+ * information from Maven repositories and presents available upgrade paths
for different Camel runtime variants.
+ * </p>
+ *
+ * <p>
+ * Command usage: list [--repos=<repos>] [--json]
+ * </p>
+ *
+ * <h3>Features:</h3>
+ * <ul>
+ * <li>Lists available update versions for Plain Camel, Camel Spring Boot, and
Camel Quarkus</li>
+ * <li>Supports additional Maven repositories for dependency resolution</li>
+ * <li>Provides output in both ASCII table and JSON formats</li>
+ * <li>Includes runtime version information and upgrade descriptions</li>
+ * </ul>
+ *
+ * <h3>Command Options:</h3>
+ * <ul>
+ * <li>--repos: Specifies additional Maven repositories for downloading
dependencies (comma-separated)</li>
+ * <li>--json: Outputs the version information in JSON format</li>
+ * </ul>
+ *
+ * <h3>Version Support:</h3>
+ * <ul>
+ * <li>Plain Camel: Supports versions from 4.8.0 onwards</li>
+ * <li>Spring Boot: Supports versions from 4.8.0 onwards</li>
+ * <li>Quarkus: Supports versions from 4.4.0 onwards with recipes from
1.0.22</li>
+ * </ul>
+ *
+ * @see org.apache.camel.dsl.jbang.core.commands.CamelCommand
+ * @see org.apache.camel.dsl.jbang.core.commands.CamelJBangMain
+ */
[email protected](name = "list",
+ description = "List available update versions for Apache
Camel and its runtime variants")
+public class UpdateList extends CamelCommand {
+
+ @CommandLine.Option(names = { "--repos" },
+ description = "Additional maven repositories for
download on-demand (Use commas to separate multiple repositories)")
+ String repos;
+
+ @CommandLine.Option(names = { "--json" },
+ description = "Output in JSON Format")
+ boolean jsonOutput;
+
+ @CommandLine.Option(names = { "--use-cache" },
+ description = "Use Maven cache")
+ boolean useCache;
+
+ private static final String CAMEL_UPGRADE_GROUPID =
"org.apache.camel.upgrade";
+ private static final String CAMEL_UPGRADE_ARTIFACTID =
"camel-upgrade-recipes";
+ private static final String CAMEL_SB_UPGRADE_ARTIFACTID =
"camel-spring-boot-upgrade-recipes";
+ private static final String FIRST_RECIPE_VERSION = "4.8.0";
+ private static final String QUARKUS_FIRST_RECIPE_VERSION = "1.0.22";
+ private static final String QUARKUS_FIRST_UPDATABLE_VERSION = "4.4.0";
+
+ public UpdateList(CamelJBangMain main) {
+ super(main);
+ }
+
+ @Override
+ public Integer doCall() throws Exception {
+ List<Row> rows = new ArrayList<>();
+ try (MavenDependencyDownloader downloader = new
MavenDependencyDownloader();) {
+ downloader.setRepositories(repos);
+ downloader.setFresh(!useCache);
+ downloader.start();
+
+ RecipeVersions recipesVersions =
collectRecipesVersions(downloader);
+
+ // Convert recipes versions into Rows for table and json
visualization
+ recipesVersions.plainCamelRecipesVersion()
+ .forEach(l -> rows
+ .add(new Row(l[0], "Camel", "", "Migrates Apache
Camel 4 application to Apache Camel " + l[0])));
+ recipesVersions.camelSpringBootRecipesVersion().forEach(l -> {
+ String[] runtimeVersion
+ = recipesVersions.sbVersions().stream().filter(v ->
v[0].equals(l[0])).findFirst().orElseThrow();
+
+ rows.add(new Row(
+ l[0], "Camel Spring Boot", runtimeVersion[1],
+ "Migrates Apache Camel Spring Boot 4 application to
Apache Camel Spring Boot " + l[0]));
+ });
+ // Translate quarkus versions to Camel
+ recipesVersions.camelQuarkusRecipesVersions();
+ recipesVersions.quarkusUpdateRecipes().forEach(l -> {
+ List<String[]> runtimeVersions =
recipesVersions.qVersions().stream().filter(v -> v[1].startsWith(l.version()))
+ .collect(Collectors.toList());
+ runtimeVersions.sort(Comparator.comparing(o -> o[1]));
+ String[] runtimeVersion =
runtimeVersions.get(runtimeVersions.size() - 1);
+ // Quarkus may release patches independently, therefore, we do
not know the real micro version
+ String quarkusVersion = runtimeVersion[1];
+ quarkusVersion = quarkusVersion.substring(0,
quarkusVersion.lastIndexOf('.')) + ".x";
+
+ rows.add(new Row(runtimeVersion[0], "Camel Quarkus",
quarkusVersion, l.description()));
+ });
+ }
+
+ rows.sort(Comparator.comparing(Row::version));
+
+ if (jsonOutput) {
+ printer().println(
+ Jsoner.serialize(
+ rows.stream().map(row -> Map.of(
+ "version", row.version(),
+ "runtime", row.runtime(),
+ "runtimeVersion", row.runtimeVersion(),
+ "description", row.description()))
+ .collect(Collectors.toList())));
+ } else {
+ printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows,
Arrays.asList(
+ new
Column().header("VERSION").minWidth(30).dataAlign(HorizontalAlign.LEFT)
+ .with(r -> r.version()),
+ new Column().header("RUNTIME")
+ .dataAlign(HorizontalAlign.LEFT).with(r ->
r.runtime()),
+ new Column().header("RUNTIME VERSION")
+ .dataAlign(HorizontalAlign.LEFT).with(r ->
r.runtimeVersion()),
+ new Column().header("DESCRIPTION")
+ .dataAlign(HorizontalAlign.LEFT).with(r ->
r.description()))));
+ }
+
+ return 0;
+ }
+
+ /**
+ * Download camel, camel-spring-boot and quarkus upgrade-recipes
dependencies and collect existing versions
+ *
+ * @param downloader
+ * @return
+ * @throws ExecutionException
+ * @throws InterruptedException
+ */
+ private RecipeVersions collectRecipesVersions(MavenDependencyDownloader
downloader)
+ throws ExecutionException, InterruptedException {
+ CompletableFuture<List<String[]>> plainCamelRecipesVersionFuture
+ = CompletableFuture.supplyAsync(() ->
downloader.resolveAvailableVersions(
+ CAMEL_UPGRADE_GROUPID,
+ CAMEL_UPGRADE_ARTIFACTID,
+ FIRST_RECIPE_VERSION,
+ repos));
+
+ final List<String[]> sbVersions = new ArrayList<>();
+ CompletableFuture<List<String[]>> camelSpringBootRecipesVersionFuture
= CompletableFuture.supplyAsync(() -> {
+ List<String[]> camelSpringBootRecipesVersion =
downloader.resolveAvailableVersions(
+ CAMEL_UPGRADE_GROUPID,
+ CAMEL_SB_UPGRADE_ARTIFACTID,
+ FIRST_RECIPE_VERSION,
+ repos);
+ if (!camelSpringBootRecipesVersion.isEmpty()) {
+ // 4.8.0 is the first version with update recipes
+ sbVersions.addAll(
+ downloader.resolveAvailableVersions(
+ "org.apache.camel.springboot",
+ "camel-spring-boot",
+ FIRST_RECIPE_VERSION,
+ repos));
+ }
+
+ return camelSpringBootRecipesVersion;
+ });
+
+ final Set<QuarkusUpdates> quarkusUpdateRecipes = new LinkedHashSet<>();
+ CompletableFuture<List<String[]>> camelQuarkusRecipesVersionsFuture =
CompletableFuture
+ .supplyAsync(() -> {
+ List<String[]> camelQuarkusRecipesVersions =
downloader.resolveAvailableVersions(
+ "io.quarkus",
+ "quarkus-update-recipes",
+ QUARKUS_FIRST_RECIPE_VERSION,
+ repos);
+
+ for (String[] camelQuarkusRecipeVersion :
camelQuarkusRecipesVersions) {
+ String version = camelQuarkusRecipeVersion[0];
+ MavenArtifact artifact =
downloader.downloadArtifact("io.quarkus",
+ "quarkus-update-recipes",
+ version);
+
+ try {
+
quarkusUpdateRecipes.addAll(getCamelQuarkusRecipesInJar(artifact.getFile()));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return camelQuarkusRecipesVersions;
+ });
+
+ CompletableFuture<List<String[]>> qVersionsFuture
+ = CompletableFuture.supplyAsync(() ->
downloader.resolveAvailableVersions(
+ "org.apache.camel.quarkus",
+ "camel-quarkus-catalog",
+ QUARKUS_FIRST_UPDATABLE_VERSION,
+ repos));
+
+ return new RecipeVersions(
+ plainCamelRecipesVersionFuture.get(),
+ sbVersions,
+ camelSpringBootRecipesVersionFuture.get(),
+ quarkusUpdateRecipes,
+ camelQuarkusRecipesVersionsFuture.get(),
+ qVersionsFuture.get());
+ }
+
+ record RecipeVersions(List<String[]> plainCamelRecipesVersion,
+ List<String[]> sbVersions,
+ List<String[]> camelSpringBootRecipesVersion,
+ Set<QuarkusUpdates> quarkusUpdateRecipes,
+ List<String[]> camelQuarkusRecipesVersions,
+ List<String[]> qVersions) {
+ }
+
+ record Row(String version, String runtime, String runtimeVersion, String
description) {
+ }
+
+ record QuarkusUpdates(String version, String description) {
+ }
+
+ /**
+ * Extracts Camel Quarkus recipe information from a JAR file.
+ *
+ * @param jar The JAR file containing Quarkus update recipes
+ * @return Collection of QuarkusUpdates containing version and
description information
+ * @throws IOException if an error occurs while reading the JAR file
+ */
+ private Collection<QuarkusUpdates> getCamelQuarkusRecipesInJar(File jar)
throws IOException {
+ List<QuarkusUpdates> quarkusUpdateRecipes = new ArrayList<>();
+ try (JarFile jarFile = new JarFile(jar)) {
+ Enumeration<JarEntry> e = jarFile.entries();
+ while (e.hasMoreElements()) {
+ JarEntry jarEntry = e.nextElement();
+ String name = jarEntry.getName();
+ if
(name.contains("quarkus-updates/org.apache.camel.quarkus/camel-quarkus/")
+ && name.endsWith(".yaml")
+ /* Quarkus specific, maybe in the future can be
removed */
+ && !name.contains("alpha")) {
+
+ String content = new
String(jarFile.getInputStream(jarEntry).readAllBytes());
+ String description =
Arrays.stream(content.split(System.lineSeparator()))
+ .filter(l -> l.startsWith("description"))
+ .map(l -> l.substring(l.indexOf(":") + 1).trim())
+ .findFirst().orElse("");
+
+ quarkusUpdateRecipes.add(new QuarkusUpdates(
+ name.substring(name.lastIndexOf("/") + 1,
name.indexOf(".yaml")),
+ description));
+ }
+ }
+
+ return quarkusUpdateRecipes;
+ }
+ }
+
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateRun.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateRun.java
new file mode 100644
index 00000000000..a31d302f825
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateRun.java
@@ -0,0 +1,136 @@
+/*
+ * 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.dsl.jbang.core.commands.update;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.RuntimeType;
+import org.apache.camel.main.download.DownloadException;
+import org.apache.camel.main.download.MavenDependencyDownloader;
+import picocli.CommandLine;
+
+/**
+ * Command to update a Camel project to a specified version. This command
supports updating projects for different
+ * runtimes such as Camel Main, Spring Boot, and Quarkus. It uses Maven and
OpenRewrite to apply the necessary updates.
+ */
[email protected](name = "run",
+ description = "Update Camel project")
+public class UpdateRun extends CamelCommand {
+
+ @CommandLine.Mixin
+ private CamelUpdateMixin updateMixin;
+
+ public UpdateRun(CamelJBangMain main) {
+ super(main);
+ }
+
+ /**
+ * Executes the update command for a Camel project. This method determines
the appropriate Maven command based on
+ * the runtime type (Camel Main, Spring Boot, or Quarkus) and executes it
to update the project to the specified
+ * version.
+ *
+ * @return the exit code of the update process (0 for success,
-1 for failure)
+ * @throws Exception if an error occurs during the update process
+ */
+ @Override
+ public Integer doCall() throws Exception {
+ // Check if the current directory contains a Maven project (i.e., a
pom.xml file)
+ if (Files.list(Path.of("."))
+ .noneMatch(f -> f.getFileName().toString().equals("pom.xml")))
{
+ printer().println("No Maven Project found in the current
directory, " +
+ "please execute camel upgrade run command in the
directory containing the Maven project to update");
+
+ return -1;
+ }
+
+ List<String> command = new ArrayList<>();
+ try (MavenDependencyDownloader downloader = new
MavenDependencyDownloader();) {
+ downloader.setRepositories(updateMixin.repos);
+ downloader.start();
+
+ Update update = null;
+ try {
+ if (updateMixin.runtime == RuntimeType.quarkus) {
+ update = new CamelQuarkusUpdate(updateMixin, downloader);
+
+ command = update.command();
+ } else {
+ update = new CamelUpdate(updateMixin, downloader);
+
+ command = update.command();
+ }
+ } catch (CamelUpdateException ex) {
+ printer().println(ex.getMessage());
+
+ return -1;
+ } catch (DownloadException e) {
+ printer().println(String.format("Cannot find Camel Upgrade
Recipes %s:%s:%s",
+ "org.apache.camel.upgrade",
update.getArtifactCoordinates(), updateMixin.version));
+
+ return -1;
+ }
+ }
+
+ executeCommand(command);
+
+ return 0;
+ }
+
+ /**
+ * Executes a shell command and prints its output.
+ *
+ * @param command the command to execute
+ * @return the exit code of the command execution
+ * @throws IOException if an I/O error occurs
+ * @throws InterruptedException if the command execution is interrupted
+ */
+ private int executeCommand(List<String> command) throws IOException,
InterruptedException {
+ ProcessBuilder pb = new ProcessBuilder(command);
+ Process p = pb.redirectErrorStream(true)
+ .start();
+
+ try (BufferedReader stdInput = new BufferedReader(new
InputStreamReader(p.getInputStream()));
+ BufferedReader stdError = new BufferedReader(new
InputStreamReader(p.getErrorStream()))) {
+
+ String line;
+ while ((line = stdInput.readLine()) != null || (line =
stdError.readLine()) != null) {
+ printer().println(line);
+ }
+
+ if (!p.waitFor(updateMixin.upgradeTimeout, TimeUnit.SECONDS)) {
+ p.destroyForcibly();
+ printer().println("Update execution timed out");
+
+ return -1;
+ }
+
+ int exitCode = p.exitValue();
+
+ return exitCode;
+
+ }
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateListTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateListTest.java
new file mode 100644
index 00000000000..f5d6569ce3a
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/update/UpdateListTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.dsl.jbang.core.commands.update;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTest;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class UpdateListTest extends CamelCommandBaseTest {
+
+ @Test
+ public void listUpdateVersions() throws Exception {
+ UpdateList infraList = new UpdateList(new
CamelJBangMain().withPrinter(printer));
+
+ infraList.doCall();
+
+ List<String> lines = printer.getLines();
+ Assertions.assertThat(lines.stream().collect(Collectors.joining("\n")))
+ .contains("Migrates Apache Camel 4 application to Apache Camel
4.9.0");
+ }
+}