diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aggregate-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aggregate-eip.adoc index db2ec5a92e58e..9a968b7654f7b 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aggregate-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/aggregate-eip.adoc @@ -114,7 +114,10 @@ be used for creating commonly used aggregation strategies without having to crea In the route below we group all the exchanges together using `GroupedExchangeAggregationStrategy`: -._Java-only: using GroupedExchangeAggregationStrategy_ +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:start") @@ -126,6 +129,44 @@ from("direct:start") .completionTimeout(500L).to("mock:result"); ---- +XML:: ++ +[source,xml] +---- + + + + + true + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - aggregate: + aggregationStrategy: groupedExchangeStrategy + completionTimeout: 500 + correlationExpression: + constant: + expression: "true" + steps: + - to: + uri: mock:result +- beans: + - name: groupedExchangeStrategy + type: org.apache.camel.processor.aggregate.GroupedExchangeAggregationStrategy +---- +==== + As a result we have one outgoing `Exchange` being routed to the `"mock:result"` endpoint. The exchange is a holder containing all the incoming Exchanges. @@ -238,6 +279,61 @@ be used at the same time. Completion is mandatory and must be configured on the aggregation. +For example, to collect the order lines of each order into a List, and send the order on when it has 10 lines, or when +no new line has arrived for 5 seconds: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:lines") + .aggregate(header("orderId"), new GroupedBodyAggregationStrategy()) + .completionSize(10) + .completionTimeout(5000) + .to("direct:order"); +---- + +XML:: ++ +[source,xml] +---- + + + + +
orderId
+
+ +
+
+---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:lines + steps: + - aggregate: + correlationExpression: + header: + expression: orderId + aggregationStrategy: groupedBodies + completionSize: 10 + completionTimeout: 5000 + steps: + - to: + uri: direct:order +- beans: + - name: groupedBodies + type: org.apache.camel.processor.aggregate.GroupedBodyAggregationStrategy +---- +==== + === Pre-completion mode There can be use-cases where you want the incoming @@ -397,7 +493,7 @@ from("direct:start") .aggregate(header("id"), new MyAggregationStrategy()) .completionSize(10).id("myAggregator") .aggregateController(controller) - .to("mock:aggregated"); + .to("mock:result"); ---- XML:: @@ -409,7 +505,7 @@ XML:: -
id
@@ -429,6 +525,7 @@ YAML:: uri: direct:start steps: - aggregate: + id: myAggregator aggregateController: myController aggregationStrategy: myAppender completionSize: 10 @@ -598,9 +695,14 @@ YAML:: ---- ==== -In Java DSL, you can also provide the bean class type directly: +You can also provide the bean class type directly. +In Java DSL you pass the class to `AggregationStrategies.bean`, and in XML or YAML DSL +you specify the bean class in `aggregationStrategy` using the `#class:` syntax as shown: -._Java-only: using AggregationStrategies.bean with class type_ +[tabs] +==== +Java:: ++ [source,java] ---- public void configure() throws Exception { @@ -611,10 +713,49 @@ public void configure() throws Exception { } ---- +XML:: ++ +[source,xml] +---- + + + + + true + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - aggregate: + aggregationStrategy: "#class:com.foo.MyBodyAppender" + aggregationStrategyMethodName: append + completionSize: 3 + correlationExpression: + constant: + expression: "true" + steps: + - to: + uri: mock:result +---- +==== + And if the bean has only one method, we do not need to specify the name of the method: -._Java-only: using AggregationStrategies.bean without method name_ +[tabs] +==== +Java:: ++ [source,java] ---- public void configure() throws Exception { @@ -625,33 +766,13 @@ public void configure() throws Exception { } ---- -And the `append` method could be static: - -._Java-only: bean class with static method_ -[source,java] ----- -public class MyBodyAppender { - - public static String append(String existing, String next) { - return next + existing; - } - -} ----- - -When using XML or YAML DSL, you can also specify the bean class directly in `aggregationStrategy` -using the `#class:` syntax as shown: - -[tabs] -==== - XML:: + [source,xml] ---- - + true @@ -670,7 +791,6 @@ YAML:: steps: - aggregate: aggregationStrategy: "#class:com.foo.MyBodyAppender" - aggregationStrategyMethodName: append completionSize: 3 correlationExpression: constant: @@ -681,17 +801,35 @@ YAML:: ---- ==== +And the `append` method could be static: + +._Java-only: bean class with static method_ +[source,java] +---- +public class MyBodyAppender { + + public static String append(String existing, String next) { + return next + existing; + } + +} +---- + === Aggregating when no data When using bean as `AggregationStrategy`, then the method is *only* invoked when there is data to be aggregated, meaning that the message body is not `null`. In cases where you want to have the method invoked, even when there are no data (message body is `null`), -then set the `strategyMethodAllowNull` to `true`. +then set the `aggregationStrategyMethodAllowNull` to `true`. -When using beans, this can be configured a bit easier using the `beanAllowNull` method -from `AggregationStrategies` as shown: +When using beans in Java DSL, this can be configured a bit easier using the `beanAllowNull` method +from `AggregationStrategies`. In XML and YAML DSL you set the `aggregationStrategyMethodAllowNull` +option to `true`, as shown: -._Java-only: using AggregationStrategies.beanAllowNull_ +[tabs] +==== +Java:: ++ [source,java] ---- public void configure() throws Exception { @@ -701,6 +839,43 @@ public void configure() throws Exception { } ---- +XML:: ++ +[source,xml] +---- + + + + seda:foo + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - pollEnrich: + expression: + constant: + expression: seda:foo + timeout: 1000 + aggregationStrategy: myAppender + aggregationStrategyMethodName: append + aggregationStrategyMethodAllowNull: true + - to: + uri: mock:result +---- +==== + Then the `append` method in the bean would need to deal with the situation that `newExchange` can be `null`: @@ -730,11 +905,21 @@ If we didn't do this, then on timeout the append method would normally not be invoked, meaning the xref:content-enricher.adoc[Content Enricher] did not merge/change the message. -In XML and YAML DSL you would configure the `strategyMethodAllowNull` option and -set it to `true` as shown below: +The same works with the aggregate EIP, as shown below: [tabs] ==== +Java:: ++ +[source,java] +---- +public void configure() throws Exception { + from("direct:start") + .aggregate(constant(true), AggregationStrategies.beanAllowNull(appender, "append")) + .completionSize(3) + .to("mock:result"); +} +---- XML:: + @@ -780,7 +965,7 @@ YAML:: === Aggregating with different body types -When, for example, using `strategyMethodAllowNull` as `true`, then the +When, for example, using `aggregationStrategyMethodAllowNull` as `true`, then the parameter type of the message bodies does not have to be the same. For example suppose we want to aggregate from a `com.foo.User` type to a `List` that contains the name of the user. We could code a bean as follows: diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cache-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cache-eip.adoc index b9cca49139629..2a293a62150a7 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cache-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/cache-eip.adoc @@ -21,6 +21,10 @@ management (bulk ops, invalidation, event listening). The Cache *EIP* covers the most common use case — _"cache the result of this expensive block by key with a TTL"_ — without manual plumbing. It is analogous to `@Cacheable` in Spring: +[tabs] +==== +Java:: ++ [source,java] ---- // Without Cache EIP: 7+ lines of plumbing, tied to one cache technology @@ -29,7 +33,7 @@ from("direct:start") .setHeader("CamelCaffeineKey", header("productId")) .to("caffeine-cache:products") .choice() - .when(body().isNull()) + .when(header("CamelCaffeineActionHasResult").isEqualTo(false)) .to("http://expensive-service") .setHeader("CamelCaffeineAction", constant("PUT")) .to("caffeine-cache:products") @@ -42,6 +46,95 @@ from("direct:start") .end(); ---- +XML:: ++ +[source,xml] +---- + + + + + GET + + +
productId
+
+ + + + ${header.CamelCaffeineActionHasResult} == false + + + PUT + + + + +
+ + + + + + ${header.productId} + + + +---- + +YAML:: ++ +// yaml-validator: skip +[source,yaml] +---- +# Without Cache EIP: manual plumbing, tied to one cache technology +- route: + from: + uri: direct:start + steps: + - setHeader: + name: CamelCaffeineAction + expression: + constant: + expression: GET + - setHeader: + name: CamelCaffeineKey + expression: + header: + expression: productId + - to: + uri: caffeine-cache:products + - choice: + when: + - expression: + simple: + expression: "${header.CamelCaffeineActionHasResult} == false" + steps: + - to: + uri: http://expensive-service + - setHeader: + name: CamelCaffeineAction + expression: + constant: + expression: PUT + - to: + uri: caffeine-cache:products + +# With Cache EIP: backend-agnostic +- route: + from: + uri: direct:start + steps: + - cache: + expression: + simple: + expression: "${header.productId}" + steps: + - to: + uri: http://expensive-service +---- +==== + == Options // eip options: START @@ -60,6 +153,10 @@ Cache errors are handled gracefully: Cache with an auto-created in-memory store (zero config): +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:start") @@ -70,6 +167,45 @@ from("direct:start") .to("direct:continue"); ---- +XML:: ++ +[source,xml] +---- + + + + ${header.productId} + + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - cache: + expression: + simple: + expression: "${header.productId}" + steps: + - to: + uri: http://product-service/api/product + - unmarshal: + json: {} + - to: + uri: direct:continue +---- +==== + Cache with TTL and an explicit `KeyValueRepository`: [tabs] @@ -105,16 +241,21 @@ YAML:: + [source,yaml] ---- -- from: - uri: "direct:start" - steps: - - cache: - simple: "${header.productId}" - keyValueRepository: "myCache" - ttl: "10m" - steps: - - to: "http://product-service/api/product" - - to: "direct:continue" +- route: + from: + uri: direct:start + steps: + - cache: + expression: + simple: + expression: "${header.productId}" + keyValueRepository: myCache + ttl: 10m + steps: + - to: + uri: http://product-service/api/product + - to: + uri: direct:continue ---- ==== @@ -151,6 +292,10 @@ public KeyValueRepository myCache() { Then reference it in the route: +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:start") @@ -160,6 +305,38 @@ from("direct:start") .end(); ---- +XML:: ++ +[source,xml] +---- + + + + ${header.key} + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - cache: + expression: + simple: + expression: "${header.key}" + keyValueRepository: myCache + steps: + - to: + uri: http://service +---- +==== + If a single `KeyValueRepository` bean exists in the registry, it is auto-discovered — no explicit reference needed. NOTE: When no `KeyValueRepository` bean is registered, each `.cache()` block auto-creates its own isolated in-memory @@ -187,6 +364,10 @@ public KeyValueRepository productCache() { } ---- +[tabs] +==== +Java:: ++ [source,java] ---- // Route that caches product lookups @@ -208,8 +389,8 @@ from("jms:topic:product.changes") .log("Invalidated cache for product ${header.productId}"); ---- -=== XML DSL - +XML:: ++ [source,xml] ---- @@ -228,10 +409,45 @@ from("jms:topic:product.changes") ---- +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:getProduct + steps: + - cache: + expression: + simple: + expression: "${header.productId}" + keyValueRepository: productCache + ttl: 30m + steps: + - to: + uri: http://product-service/api/product + +# Invalidation route +- route: + from: + uri: jms:topic:product.changes + steps: + - bean: + ref: productCache + method: "delete(${header.productId})" + - log: + message: "Invalidated cache for product ${header.productId}" +---- +==== + === Clearing the Entire Cache To clear all entries from a `KeyValueRepository`, call `clear()`: +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:clearAll") @@ -241,3 +457,27 @@ from("direct:clearAll") cache.clear(); }); ---- + +XML:: ++ +[source,xml] +---- + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:clearAll + steps: + - bean: + ref: productCache + method: clear +---- +==== diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/choice-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/choice-eip.adoc index c9f0645698c2d..4a42cbb1ef27f 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/choice-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/choice-eip.adoc @@ -220,7 +220,7 @@ The Camel xref:languages:simple-language.adoc[Simple] language is great to use with the Choice EIP in precondition mode to select a specific branch based on xref:manual:ROOT:using-propertyplaceholder.adoc[property placeholders]. -Here we select from the Switch the first predicate that matches. So if there is +Here we select from the Choice the first predicate that matches. So if there is a property placeholder with the key `foo` then it is selected, and so on. Notice how we can use `{{?foo}}` to mark the property placeholder as optional. diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/circuitBreaker-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/circuitBreaker-eip.adoc index 21d3a5f87e5af..afc040ed39e1b 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/circuitBreaker-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/circuitBreaker-eip.adoc @@ -49,38 +49,7 @@ what happened to the call itself: `CamelCircuitBreakerState` is the state of the breaker after the call was recorded, so the call that opens the breaker reports `OPEN` although it was attempted; use the rejected property to know whether a call was made. The caught exception is available in the fallback, for example as `${exception.message}` in the Simple language. - -For example, a fallback that answers differently when the service is down rather than failing once: - -[source,yaml] ----- -- route: - from: - uri: timer:stock?period=1000 - steps: - - circuitBreaker: - steps: - - to: - uri: direct:supplier - onFallback: - steps: - - choice: - when: - - expression: - simple: - expression: "${exchangeProperty.CamelCircuitBreakerResponseRejected} == true" - steps: - - setBody: - expression: - constant: - expression: "supplier is down, using the last known stock" - otherwise: - steps: - - setBody: - expression: - constant: - expression: "stock check failed, will try again" ----- +The example under _Using the exchange properties in the fallback_ below uses these properties. == Example @@ -145,6 +114,89 @@ YAML:: ---- ==== +=== Using the exchange properties in the fallback + +A fallback that answers differently when the service is down rather than failing once: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("timer:stock?period=1000") + .circuitBreaker() + .to("direct:supplier") + .onFallback() + .choice() + .when(simple("${exchangeProperty.CamelCircuitBreakerResponseRejected} == true")) + .setBody(constant("supplier is down, using the last known stock")) + .otherwise() + .setBody(constant("stock check failed, will try again")) + .end() + .end(); +---- + +XML:: ++ +[source,xml] +---- + + + + + + + + ${exchangeProperty.CamelCircuitBreakerResponseRejected} == true + + supplier is down, using the last known stock + + + + + stock check failed, will try again + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: timer:stock?period=1000 + steps: + - circuitBreaker: + steps: + - to: + uri: direct:supplier + onFallback: + steps: + - choice: + when: + - expression: + simple: + expression: "${exchangeProperty.CamelCircuitBreakerResponseRejected} == true" + steps: + - setBody: + expression: + constant: + expression: "supplier is down, using the last known stock" + otherwise: + steps: + - setBody: + expression: + constant: + expression: "stock check failed, will try again" +---- +==== + == Route lifecycle and breaker state When a route is stopped and started, the circuit breaker state is reset (an OPEN breaker returns to CLOSED) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/claimCheck-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/claimCheck-eip.adoc index da500600b3c0e..4582a9a513089 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/claimCheck-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/claimCheck-eip.adoc @@ -136,104 +136,6 @@ You can also remove headers when merging data back. For example, to remove all h Note you cannot have both include (`+`) and exclude (`-`) `header:pattern` at the same time. -== Dynamic keys - -The claim check keys are static, but you can use the `simple` language syntax to define dynamic keys. -For example, to use a header from the message named `myKey`: - -[tabs] -==== -Java:: -+ -[source,java] ----- -from("direct:start") - .to("mock:a") - .claimCheck(ClaimCheckOperation.Set, "${header.myKey}") - .transform().constant("Bye World") - .to("mock:b") - .claimCheck(ClaimCheckOperation.Get, "${header.myKey}") - .to("mock:c") - .transform().constant("Hi World") - .to("mock:d") - .claimCheck(ClaimCheckOperation.Get, "${header.myKey}") - .to("mock:e"); ----- - -XML:: -+ -[source,xml] ----- - - - - - - Bye World - - - - - - Hi World - - - - - ----- - -YAML:: -+ -[source,yaml] ----- -- route: - from: - uri: direct - parameters: - name: start - steps: - - to: - uri: mock - parameters: - name: a - - claimCheck: - operation: Set - key: "${header.myKey}" - - transform: - expression: - constant: - expression: Bye World - - to: - uri: mock - parameters: - name: b - - claimCheck: - operation: Get - key: "${header.myKey}" - - to: - uri: mock - parameters: - name: c - - transform: - expression: - constant: - expression: Hi World - - to: - uri: mock - parameters: - name: d - - claimCheck: - operation: Get - key: "${header.myKey}" - - to: - uri: mock - parameters: - name: e ----- - -==== - == Example The following example shows the `Push` and `Pop` operations in action: @@ -451,3 +353,101 @@ YAML:: uri: mock:c ---- ==== + +== Dynamic keys + +The claim check keys are static, but you can use the `simple` language syntax to define dynamic keys. +For example, to use a header from the message named `myKey`: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .to("mock:a") + .claimCheck(ClaimCheckOperation.Set, "${header.myKey}") + .transform().constant("Bye World") + .to("mock:b") + .claimCheck(ClaimCheckOperation.Get, "${header.myKey}") + .to("mock:c") + .transform().constant("Hi World") + .to("mock:d") + .claimCheck(ClaimCheckOperation.Get, "${header.myKey}") + .to("mock:e"); +---- + +XML:: ++ +[source,xml] +---- + + + + + + Bye World + + + + + + Hi World + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct + parameters: + name: start + steps: + - to: + uri: mock + parameters: + name: a + - claimCheck: + operation: Set + key: "${header.myKey}" + - transform: + expression: + constant: + expression: Bye World + - to: + uri: mock + parameters: + name: b + - claimCheck: + operation: Get + key: "${header.myKey}" + - to: + uri: mock + parameters: + name: c + - transform: + expression: + constant: + expression: Hi World + - to: + uri: mock + parameters: + name: d + - claimCheck: + operation: Get + key: "${header.myKey}" + - to: + uri: mock + parameters: + name: e +---- + +==== diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/content-enricher.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/content-enricher.adoc index ce81a8f04875a..e652a28767daa 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/content-enricher.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/content-enricher.adoc @@ -108,18 +108,14 @@ YAML:: ---- - route: from: - uri: direct - parameters: - name: start + uri: direct:start steps: - setBody: expression: simple: expression: "${body} World!" - to: - uri: mock - parameters: - name: result + uri: mock:result ---- ==== @@ -188,16 +184,12 @@ YAML:: ---- - route: from: - uri: direct - parameters: - name: start + uri: direct:start steps: - process: ref: myProcessor - to: - uri: mock - parameters: - name: result + uri: mock:result ---- ==== @@ -223,9 +215,9 @@ XML:: [source,xml] ---- - + - + ---- @@ -235,13 +227,13 @@ YAML:: ---- - route: from: - uri: activemq:Input + uri: activemq:My.Queue steps: - bean: ref: myBeanName method: doTransform - to: - uri: activemq:Output + uri: activemq:Another.Queue ---- ==== diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/content-filter-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/content-filter-eip.adoc index 355a53ac163a6..ddaa880c2ba31 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/content-filter-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/content-filter-eip.adoc @@ -15,6 +15,54 @@ image::eip/ContentFilter.gif[image] * Using a xref:manual::processor.adoc[Processor] with the filtering programmed in Java * Using an xref:manual::expression.adoc[Expression] +== Message Content filtering using expression + +Some languages like xref:languages:xpath-language.adoc[XPath], and xref:languages:xquery-language.adoc[XQuery] +can be used to transform and filter content from messages. + +In the example we use xpath to filter a XML message to select all the `` elements: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("activemq:Input") + .setBody().xpath("//foo/bar") + .to("activemq:Output"); +---- + +XML:: ++ +[source,xml] +---- + + + + //foo/bar + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: activemq:Input + steps: + - setBody: + expression: + xpath: + expression: //foo/bar + - to: + uri: activemq:Output +---- +==== + == Message Content filtering using a Processor In this example, we add our own xref:manual::processor.adoc[Processor] using @@ -84,16 +132,12 @@ YAML:: ---- - route: from: - uri: direct - parameters: - name: start + uri: direct:start steps: - process: ref: myProcessor - to: - uri: mock - parameters: - name: result + uri: mock:result ---- ==== @@ -109,9 +153,9 @@ Java:: + [source,java] ---- -from("activemq:My.Queue") +from("activemq:Input") .bean("myBeanName", "doFilter") - .to("activemq:Another.Queue"); + .to("activemq:Output"); ---- XML:: @@ -140,50 +184,3 @@ YAML:: uri: activemq:Output ---- ==== - -== Message Content filtering using expression - -Some languages like xref:languages:xpath-language.adoc[XPath], and xref:languages:xquery-language.adoc[XQuery] -can be used to transform and filter content from messages. - -In the example we use xpath to filter a XML message to select all the `` elements: - -[tabs] -==== -Java:: -+ -[source,java] ----- -from("activemq:Input") - .setBody().xpath("//foo:bar") - .to("activemq:Output"); ----- - -XML:: -+ -[source,xml] ----- - - - - //foo:bar - - - ----- - -YAML:: -+ -[source,yaml] ----- -- route: - from: - uri: activemq:Input - steps: - - setBody: - expression: - xpath: //foo:bar - - to: - uri: activemq:Output ----- -==== diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc index 2df230324a93a..b6148e33bb4b4 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc @@ -61,9 +61,23 @@ Pay attention to Spring XML DSL. The type attribute is used to declare which err And in XML the `` must be configured with an id. The id must then be enabled on either the `` (global scope), or per route that should use this error handler. -The following listing is an equivalent of the prior Java DSL listing: +The following listing shows the error handler together with a route: -._XML-only:_ +[tabs] +==== +Java:: ++ +[source,java] +---- +errorHandler(deadLetterChannel("log:dead?level=ERROR")); + +from("direct:newOrder") + .bean("orderService", "validate") + .bean("orderService", "store"); +---- + +Spring XML:: ++ [source,xml] ---- @@ -77,9 +91,30 @@ The following listing is an equivalent of the prior Java DSL listing: ---- -Notice how we have enabled the error handler on the `` via `errorHandlerRef` +YAML:: ++ +[source,yaml] +---- +- errorHandler: + deadLetterChannel: + deadLetterUri: "log:dead?level=ERROR" +- route: + from: + uri: direct:newOrder + steps: + - bean: + ref: orderService + method: validate + - bean: + ref: orderService + method: store +---- +==== + +Notice how in Spring XML we have enabled the error handler on the `` via `errorHandlerRef` which means this error handler is used globally. You can override this per route, and have individual routes used another error handler. +In Java and YAML, the error handler applies to the routes in the same `RouteBuilder` class or YAML file. NOTE: The DSLs is planned to be improved in the near future to have a unified way of configuring error handling across all DSLs with xref:manual::route-configuration.adoc[Route Configuration]. @@ -264,7 +299,7 @@ Java:: ---- // will use the original message (body and headers) errorHandler(deadLetterChannel("jms:queue:dead") - .useOriginalMessage().maximumRedeliveries(5).redeliveryDelay(5000); + .useOriginalMessage().maximumRedeliveries(5).redeliveryDelay(5000)); ---- Spring XML:: @@ -324,7 +359,7 @@ Java:: ---- errorHandler(deadLetterChannel("jms:queue:dead") .maximumRedeliveries(3) - .onRedelivery(new MyOnRedeliveryProcessor())); + .onRedelivery(new MyRedeliveryProcessor())); ---- Spring XML:: @@ -396,8 +431,7 @@ Configuring this from Spring XML is done with the `onPrepareFailureRef` to refer + [source,xml] ---- - + ---- diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/delay-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/delay-eip.adoc index 53c147d95a68f..ad22500d3731a 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/delay-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/delay-eip.adoc @@ -69,9 +69,9 @@ Note that delay creates its own block, so some DSLs (including Java) require the ._Java-only: closing the delay block with end() in Java DSL_ [source,java] ---- -.from("direct:delayBlockExample") +from("direct:delayBlockExample") .to("direct:getJobState") - .loopDoWhile(simple("${body.state} = 'NOT_DONE'")) + .loopDoWhile(simple("${body.state} == 'NOT_DONE'")) .log(LoggingLevel.DEBUG, "Pausing") .delay(10000) .syncDelayed() diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/idempotentConsumer-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/idempotentConsumer-eip.adoc index 29c614a03d459..d4f2a23cbc95e 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/idempotentConsumer-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/idempotentConsumer-eip.adoc @@ -59,4 +59,140 @@ xref:ROOT:sql-component.adoc[JdbcOrphanLockAwareIdempotentRepository] == Example -For example, see the above implementations for more details. +The idempotent consumer needs an expression that gives the id of each message, and a repository that remembers the ids +it has seen. In the example below the id is the `messageId` header, and the repository is a `MemoryIdempotentRepository`, +which keeps the last 1000 ids in memory. A message with an id that is already in the repository is skipped, so it does +not reach `mock:result`. + +In XML and YAML the repository is a bean, and the idempotent consumer refers to it by name. + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .idempotentConsumer(header("messageId"), MemoryIdempotentRepository.memoryIdempotentRepository()) + .to("mock:result"); +---- + +XML:: ++ +[source,xml] +---- + + + + + +
messageId
+ +
+
+---- + +YAML:: ++ +[source,yaml] +---- +- beans: + - name: myRepo + type: org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository +- route: + from: + uri: direct:start + steps: + - idempotentConsumer: + expression: + header: + expression: messageId + idempotentRepository: myRepo + steps: + - to: + uri: mock:result +---- +==== + +The memory repository forgets the ids when the application stops. To keep them, use `FileIdempotentRepository` or one +of the repositories listed above. + +=== Handling the duplicates in the route + +By default, a duplicate is skipped. When `skipDuplicate` is `false`, a duplicate goes through the idempotent consumer +too, and the exchange property `CamelDuplicateMessage` is `true`. The route can then decide what to do with it, for +example log it: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .idempotentConsumer(header("messageId"), MemoryIdempotentRepository.memoryIdempotentRepository()) + .skipDuplicate(false) + .choice() + .when(simple("${exchangeProperty.CamelDuplicateMessage} == true")) + .log("Message ${header.messageId} is a duplicate") + .otherwise() + .to("mock:result") + .end() + .end(); +---- + +XML:: ++ +[source,xml] +---- + + + + + +
messageId
+ + + ${exchangeProperty.CamelDuplicateMessage} == true + + + + + + +
+
+---- + +YAML:: ++ +[source,yaml] +---- +- beans: + - name: myRepo + type: org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository +- route: + from: + uri: direct:start + steps: + - idempotentConsumer: + expression: + header: + expression: messageId + idempotentRepository: myRepo + skipDuplicate: false + steps: + - choice: + when: + - expression: + simple: + expression: "${exchangeProperty.CamelDuplicateMessage} == true" + steps: + - log: + message: "Message ${header.messageId} is a duplicate" + otherwise: + steps: + - to: + uri: mock:result +---- +==== diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/kamelet-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/kamelet-eip.adoc index 16c97466223d1..d89489cc3fbdc 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/kamelet-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/kamelet-eip.adoc @@ -72,6 +72,7 @@ XML:: YAML:: + +// yaml-validator: skip [source,yaml] ---- - route: diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/log-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/log-eip.adoc index 41980bb8df0eb..faa35aa7cd9ba 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/log-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/log-eip.adoc @@ -276,7 +276,7 @@ Java:: Logger myLogger = org.slf4j.LoggerFactory.getLogger("com.mycompany.mylogger"); from("direct:moo") - .log(myLogger, "Me Got ${body}") + .log(LoggingLevel.INFO, myLogger, "Me Got ${body}") .to("bean:foo"); ---- @@ -284,14 +284,16 @@ XML:: + [source,xml] ---- - - + + + + - + - + ---- @@ -300,7 +302,6 @@ YAML:: [source,yaml] ---- - route: - id: moo from: uri: direct:moo steps: @@ -308,7 +309,13 @@ YAML:: logger: myLogger message: "Me Got ${body}" - to: - uri: mock:baz + uri: bean:foo +- beans: + - name: myLogger + type: org.slf4j.LoggerFactory + factoryMethod: getLogger + constructors: + 0: com.mycompany.mylogger ---- ==== diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/marshal-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/marshal-eip.adoc index 398f85725e39f..5583162409dbd 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/marshal-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/marshal-eip.adoc @@ -24,7 +24,48 @@ into a Java object; or some other representation according to the data format be == Example -The following example reads XML files from the inbox/xml directory. +The following example sends an order as JSON to a JMS queue. The message body is an object, such as a Map or a POJO, +and `marshal` turns it into JSON text with xref:dataformats:jackson2-dataformat.adoc[Jackson]: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:order") + .marshal().json(JsonLibrary.Jackson) + .to("jms:queue:order"); +---- + +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:order + steps: + - marshal: + json: + library: Jackson + - to: + uri: jms:queue:order +---- +==== + +The next example reads XML files from the inbox/xml directory. Each file is then transformed into Java Objects using xref:dataformats:jaxb-dataformat.adoc[JAXB]. Then a xref:ROOT:bean-component.adoc[Bean] is invoked that takes in the Java object. diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pausable-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pausable-eip.adoc index 20bc377177a84..cdbacc58be292 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pausable-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pausable-eip.adoc @@ -33,11 +33,49 @@ from("kafka:topic") .process(exchange -> LOG.info("Received: {}", exchange.getMessage().getBody())) .to("direct:destination"); ---- + +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: kafka:topic + steps: + - pausable: + consumerListener: kafkaConsumerListener + untilCheck: canContinue + - log: + message: "Received: ${body}" + - to: + uri: direct:destination +- beans: + - name: kafkaConsumerListener + type: org.apache.camel.component.kafka.consumer.errorhandler.KafkaConsumerListener + - name: canContinue + type: com.foo.CanContinuePredicate +---- ==== You can also integrate the Pausable EIP with the xref:circuitBreaker-eip.adoc[Circuit Breaker EIP] to pause consumption based on downstream system availability: +[tabs] +==== +Java:: ++ [source,java] ---- from("kafka:topic") @@ -48,6 +86,40 @@ from("kafka:topic") .end(); ---- +XML:: ++ +[source,xml] +---- + + + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: kafka:topic + steps: + - pausable: + consumerListener: kafkaConsumerListener + untilCheck: canContinue + - circuitBreaker: + resilience4jConfiguration: + circuitBreaker: pausableCircuit + steps: + - to: + uri: direct:downstream +---- +==== + == See Also * xref:resume-strategies.adoc[Resume Strategies] diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pollEnrich-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pollEnrich-eip.adoc index 3dfccbcd508e0..2ad4c0890e0a9 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pollEnrich-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/pollEnrich-eip.adoc @@ -74,6 +74,100 @@ You can pass in a timeout value that determines which method to use: The timeout values are in milliseconds. +For example, to read the file `data.txt` from the inbox directory, waiting up to 10 seconds for it. Without an +`AggregationStrategy`, the polled message is the result, so the content of the file becomes the message body: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .pollEnrich("file:inbox?fileName=data.txt", 10000) + .to("mock:result"); +---- + +XML:: ++ +[source,xml] +---- + + + + file:inbox?fileName=data.txt + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - pollEnrich: + timeout: 10000 + expression: + constant: + expression: file:inbox?fileName=data.txt + - to: + uri: mock:result +---- +==== + +=== Poll Enrich with Dynamic Endpoints + +Both `enrich` and `pollEnrich` supports using dynamic uris computed based on information from the current `Exchange`. + +For example to `pollEnrich` from an endpoint that uses a header to indicate a SEDA queue name: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .pollEnrich().simple("seda:${header.queueName}") + .to("direct:result"); +---- + +XML:: ++ +[source,xml] +---- + + + + seda:${header.queueName} + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - pollEnrich: + expression: + simple: + expression: "seda:${header.queueName}" + - to: + uri: direct:result +---- +==== + +TIP: See the `cacheSize` option for more details on _how much cache_ to use depending on how many or few unique endpoints are used. + === Using Poll Enrich The content enricher (`pollEnrich`) retrieves additional data from a _resource endpoint_ in order to enrich an incoming message (contained in the _original exchange_). @@ -218,55 +312,6 @@ YAML:: Notice that the enriched endpoint is a constant, however, Camel also supports dynamic endpoints which is covered next. -=== Poll Enrich with Dynamic Endpoints - -Both `enrich` and `pollEnrich` supports using dynamic uris computed based on information from the current `Exchange`. - -For example to `pollEnrich` from an endpoint that uses a header to indicate a SEDA queue name: - -[tabs] -==== -Java:: -+ -[source,java] ----- -from("direct:start") - .pollEnrich().simple("seda:${header.queueName}") - .to("direct:result"); ----- - -XML:: -+ -[source,xml] ----- - - - - seda:${header.queueName} - - - ----- - -YAML:: -+ -[source,yaml] ----- -- route: - from: - uri: direct:start - steps: - - pollEnrich: - expression: - simple: - expression: "seda:${header.queueName}" - - to: - uri: direct:result ----- -==== - -TIP: See the `cacheSize` option for more details on _how much cache_ to use depending on how many or few unique endpoints are used. - === Using Poll Enrich with file based components When using `poll` or `pollEnrich` with the file based components, then the `eagerLimitMaxMessagesPerPoll` option diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/polling-consumer.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/polling-consumer.adoc index d06b471755975..152e13341cbb5 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/polling-consumer.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/polling-consumer.adoc @@ -44,6 +44,7 @@ You can programmatically create an instance of `PollingConsumer` from any endpoi ---- Endpoint endpoint = context.getEndpoint("activemq:my.queue"); PollingConsumer consumer = endpoint.createPollingConsumer(); +consumer.start(); Exchange exchange = consumer.receive(); ---- @@ -96,6 +97,7 @@ as shown below: Endpoint endpoint = context.getEndpoint("file:inbox?pollingConsumerQueueSize=50"); PollingConsumer consumer = endpoint.createPollingConsumer(); +consumer.start(); Exchange exchange = consumer.receive(5000); ---- diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/process-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/process-eip.adoc index 654b09187ac4b..d4acf724f9f2f 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/process-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/process-eip.adoc @@ -66,13 +66,44 @@ public class MyProcessor implements Processor { Then in Camel you can call this processor: -._Java-only: calling a Processor by instantiating it directly_ +[tabs] +==== +Java:: ++ [source,java] ---- from("activemq:myQueue") .process(new MyProcessor()); ---- +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: activemq:myQueue + steps: + - process: + ref: myProcessor +- beans: + - name: myProcessor + type: com.foo.MyProcessor +---- +==== + You can also call a processor by its bean id, if the processor has been enlisted in the xref:manual::registry.adoc[Registry], such as with the id `myProcessor`: diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/recipientList-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/recipientList-eip.adoc index f0efa65b63678..5a734e2c8e7a5 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/recipientList-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/recipientList-eip.adoc @@ -143,10 +143,23 @@ The dynamic list of recipients that are defined in the header must be iterable s === Configuring delimiter for dynamic destinations -In XML DSL you can set the delimiter attribute for setting a delimiter to be used if the header value is a single `String` with multiple separated endpoints. +You can set the `delimiter` option for setting a delimiter to be used if the header value is a single `String` with multiple separated endpoints. By default, Camel uses comma as delimiter, but this option lets you specify a custom delimiter to use instead. -._XML-only:_ +[tabs] +==== +Java:: ++ +In Java DSL, you specify the delimiter as second parameter: ++ +[source,java] +---- +from("direct:a") + .recipientList(header("myHeader"), ";"); +---- + +XML:: ++ [source,xml] ---- @@ -158,8 +171,8 @@ By default, Camel uses comma as delimiter, but this option lets you specify a cu ---- -And in YAML DSL you set the `delimiter` parameter: - +YAML:: ++ [source,yaml] ---- - route: @@ -172,21 +185,13 @@ And in YAML DSL you set the `delimiter` parameter: header: expression: myHeader ---- +==== So if *myHeader* contains a `String` with the value `"activemq:queue:foo;activemq:topic:hello ; log:bar"` -then Camel will split the `String` using the delimiter given in the XML that was comma, +then Camel will split the `String` using the delimiter, which is a semicolon, resulting into three endpoints to send to. You can use spaces between the endpoints as Camel will trim the value when it looks up the endpoint to send to. -In Java DSL, you specify the delimiter as second parameter as shown below: - -._Java-only: the delimiter is passed as a second parameter to recipientList_ -[source,java] ----- -from("direct:a") - .recipientList(header("myHeader"), ";"); ----- - === Using parallel processing The Recipient List supports `parallelProcessing` similar to what xref:multicast-eip.adoc[Multicast] and xref:split-eip.adoc[Split] EIPs have as well. @@ -415,10 +420,13 @@ So use this option with a bit of care. ==== For example, in the unit test below, you can see that we multicast the message to three destinations. -We have a timeout of 2 seconds, which means only the last two messages can be completed within the timeframe. +We have a timeout of 250 milliseconds, which means only the last two messages can be completed within the timeframe. This means we will only aggregate the last two which yields a result aggregation which outputs "BC". -._Java-only: multicast with inline AggregationStrategy and timeout_ +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:start") @@ -445,6 +453,111 @@ from("direct:b").to("mock:B").setBody(constant("B")); from("direct:c").to("mock:C").setBody(constant("C")); ---- +XML:: ++ +[source,xml] +---- + + + + + + + + + + + + + + + 1000 + + + + A + + + + + + + + B + + + + + + + + C + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - multicast: + aggregationStrategy: myStrategy + parallelProcessing: true + timeout: 250 + steps: + - to: + uri: direct:a + - to: + uri: direct:b + - to: + uri: direct:c + - to: + uri: mock:result +- route: + from: + uri: direct:a + steps: + - delay: + expression: + constant: + expression: "1000" + - to: + uri: mock:A + - setBody: + expression: + constant: + expression: A +- route: + from: + uri: direct:b + steps: + - to: + uri: mock:B + - setBody: + expression: + constant: + expression: B +- route: + from: + uri: direct:c + steps: + - to: + uri: mock:C + - setBody: + expression: + constant: + expression: C +- beans: + - name: myStrategy + type: com.foo.MyAggregationStrategy +---- +==== + By default, if a timeout occurs the `AggregationStrategy` is not invoked. However, you can implement the `timeout` method: This allows you to deal with the timeout in the `AggregationStrategy` if you really need to. diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/resumable-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/resumable-eip.adoc index cd2c2c679afbe..b1a8191f2aa73 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/resumable-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/resumable-eip.adoc @@ -37,12 +37,45 @@ from("some:component") .resumable().configuration(kafkaConfigurationBuilder) .process(this::process); ---- + +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: some:component + steps: + - resumable: + resumeStrategy: kafkaResumeStrategy + - process: + ref: myProcessor +---- ==== +NOTE: The configuration builder is only available in the Java DSL. +In XML and YAML, the `resumeStrategy` option refers to a resume strategy bean by its name. +In this example, `kafkaResumeStrategy` is a `SingleNodeKafkaResumeStrategy` bean set up with the same configuration. + === Intermittent Mode Enable intermittent mode to avoid updating the offset for every exchange: +[tabs] +==== +Java:: ++ [source,java] ---- from("some:component") @@ -50,6 +83,36 @@ from("some:component") .process(this::process); ---- +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: some:component + steps: + - resumable: + resumeStrategy: testResumeStrategy + intermittent: true + - process: + ref: myProcessor +- beans: + - name: testResumeStrategy + type: com.foo.MyTestResumeStrategy +---- +==== + == See Also * xref:resume-strategies.adoc[Resume Strategies] diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/resume-strategies.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/resume-strategies.adoc index 569e0b61619e9..a29e649658209 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/resume-strategies.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/resume-strategies.adoc @@ -4,6 +4,7 @@ :description: Provide strategies to allow consuming data from specific offsets :since: 3.16.0 :supportlevel: Experimental +:tabs-sync-option: The resume strategies allow users to implement strategies that point the consumer part of the routes to the last point of consumption. This allows Camel to skip reading and processing data that has already been consumed. @@ -35,7 +36,7 @@ The resume strategies comes in three parts: == The DSL method The route needs to use the `resumable()` method followed by passing a strategy configuration using `configuration`. -It is also possible to use the `resumableStrategy` to point to an instance of the resume strategy in use, although +It is also possible to use the `resumeStrategy` to point to an instance of the resume strategy in use, although this is much more complex. The vast majority of the cases should use a `configuration`, in which case Camel will do the heavy-lifting for you. @@ -60,7 +61,10 @@ from("some:component") This instance can be bound in the Context registry as follows: -._Java-only: binding resume strategy to the registry_ +[tabs] +==== +Java:: ++ [source,java] ---- getCamelContext().getRegistry().bind("testResumeStrategy", new MyTestResumeStrategy()); @@ -71,17 +75,97 @@ from("some:component") .process(this::process); ---- +XML:: ++ +[source,xml] +---- + + + + + + + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- beans: + - name: testResumeStrategy + type: com.foo.MyTestResumeStrategy + - name: resumeCache + type: com.foo.MyChoiceOfResumeCache + constructors: + 0: 100 +- route: + from: + uri: some:component + steps: + - resumable: + resumeStrategy: testResumeStrategy + - process: + ref: myProcessor +---- +==== + Or the instance can be constructed as follows: -._Java-only: constructing resume strategy inline_ +[tabs] +==== +Java:: ++ [source,java] ---- getCamelContext().getRegistry().bind("resumeCache", new MyChoiceOfResumeCache<>(100)); from("some:component") .resumable(new MyTestResumeStrategy()) - .process(this::process) + .process(this::process); +---- + +XML:: ++ +[source,xml] +---- + + + + + + + + + + + +---- + +YAML:: ++ +[source,yaml] ---- +- beans: + - name: resumeCache + type: com.foo.MyChoiceOfResumeCache + constructors: + 0: 100 +- route: + from: + uri: some:component + steps: + - resumable: + resumeStrategy: "#class:com.foo.MyTestResumeStrategy" + - process: + ref: myProcessor +---- +==== In some circumstances, such as when dealing with File I/O, it may be necessary to set the offset manually. There are *supporting classes* that can help work with resumables: @@ -94,13 +178,46 @@ In some circumstances, such as when dealing with File I/O, it may be necessary t In some cases, it may be necessary to avoid updating the offset for every exchange. You can enable the intermittent mode to modify the route behavior so that missing offsets will not cause an exception: -._Java-only: enabling intermittent mode on resumable_ +[tabs] +==== +Java:: ++ [source,java] ---- from("some:component") -.resumable().resumeStrategy(new MyTestResumeStrategy()).intermittent(true) -.process(this::process) + .resumable().resumeStrategy(new MyTestResumeStrategy()).intermittent(true) + .process(this::process); +---- + +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: some:component + steps: + - resumable: + resumeStrategy: testResumeStrategy + intermittent: true + - process: + ref: myProcessor +- beans: + - name: testResumeStrategy + type: com.foo.MyTestResumeStrategy ---- +==== == Builtin Resume Strategies @@ -140,7 +257,10 @@ When using the converters with the file component, beware of the differences in For instance, the behavior of: -._Java-only: resumable with Reader body type_ +[tabs] +==== +Java:: ++ [source,java] ---- from("file:{{input.dir}}?noop=true&fileName={{input.file}}") @@ -149,9 +269,44 @@ from("file:{{input.dir}}?noop=true&fileName={{input.file}}") .process(this::process); ---- +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: "file:{{input.dir}}" + parameters: + noop: true + fileName: "{{input.file}}" + steps: + - resumable: + resumeStrategy: testResumeStrategy + - convertBodyTo: + type: java.io.Reader + - process: + ref: myProcessor +---- +==== + It is different from the behavior of: -._Java-only: resumable with InputStream body type_ +[tabs] +==== +Java:: ++ [source,java] ---- from("file:{{input.dir}}?noop=true&fileName={{input.file}}") @@ -160,6 +315,38 @@ from("file:{{input.dir}}?noop=true&fileName={{input.file}}") .process(this::process); ---- +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: "file:{{input.dir}}" + parameters: + noop: true + fileName: "{{input.file}}" + steps: + - resumable: + resumeStrategy: testResumeStrategy + - convertBodyTo: + type: java.io.InputStream + - process: + ref: myProcessor +---- +==== + *Reason*: the `skip` method in the Reader will skip characters, whereas the same method on the InputStream will skip bytes. @@ -180,14 +367,52 @@ To use the API, it needs an instance of a Consumer listener along with a predica Usage example: -._Java-only: using pausable consumer API_ +[tabs] +==== +Java:: ++ [source,java] ---- -from(from) +from("kafka:topic") .pausable(new KafkaConsumerListener(), o -> canContinue()) .process(exchange -> LOG.info("Received an exchange: {}", exchange.getMessage().getBody())) - .to(destination); + .to("direct:destination"); +---- + +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: kafka:topic + steps: + - pausable: + consumerListener: kafkaConsumerListener + untilCheck: canContinue + - log: + message: "Received an exchange: ${body}" + - to: + uri: direct:destination +- beans: + - name: kafkaConsumerListener + type: org.apache.camel.component.kafka.consumer.errorhandler.KafkaConsumerListener + - name: canContinue + type: com.foo.CanContinuePredicate ---- +==== You can also integrate the pausable API and the consumer listener with the circuit breaker EIP. For instance, it's possible to configure the circuit breaker so that it can manipulate the state of the listener based on success or on @@ -199,7 +424,7 @@ On success, it shuts down the scheduled check. An example implementation of this approach would be similar to this: -._Java-only: integrating pausable consumer with circuit breaker_ +._Java-only: creating the circuit breaker and binding it to the registry_ [source,java] ---- CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("pausable"); @@ -218,21 +443,68 @@ circuitBreaker.getEventPublisher() if (executorService == null) { executorService = Executors.newSingleThreadScheduledExecutor(); - // In a real world scenario, instead of incrementing, it could be pinging a remote system or - // running a similar check to determine whether it's available. That + // In a real world scenario, it could be pinging a remote system or + // running a similar check to determine whether it's available. executorService.scheduleAtFixedRate(() -> someCheckMethod(), 1, 1, TimeUnit.SECONDS); } }); // Binds the configuration to the registry - getCamelContext().getRegistry().bind("pausableCircuit", circuitBreaker); +getCamelContext().getRegistry().bind("pausableCircuit", circuitBreaker); +---- -from(from) +The route then uses the circuit breaker by its name in the registry, `pausableCircuit`: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("kafka:topic") .pausable(new KafkaConsumerListener(), o -> canContinue()) .routeId("pausable-it") .process(exchange -> LOG.info("Got record from Kafka: {}", exchange.getMessage().getBody())) .circuitBreaker() .resilience4jConfiguration().circuitBreaker("pausableCircuit").end() - .to(to) + .to("direct:downstream") .end(); ---- + +XML:: ++ +[source,xml] +---- + + + + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + id: pausable-it + from: + uri: kafka:topic + steps: + - pausable: + consumerListener: kafkaConsumerListener + untilCheck: canContinue + - log: + message: "Got record from Kafka: ${body}" + - circuitBreaker: + resilience4jConfiguration: + circuitBreaker: pausableCircuit + steps: + - to: + uri: direct:downstream +---- +==== diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/setHeaders-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/setHeaders-eip.adoc index 2be3b1e8be27d..75cff6e97242e 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/setHeaders-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/setHeaders-eip.adoc @@ -152,13 +152,13 @@ XML:: - - ${body} - - - ${header.foo} > 10 - - + + ${body} + + + ${header.foo} > 10 + + ---- @@ -195,7 +195,7 @@ If the order in which the headers should be set is important, use a `LinkedHashM ._Java-only: using a LinkedHashMap to set headers in order_ [source,java] ---- -private Map headerMap = new java.util.LinkedHashMap<>(); +Map headerMap = new java.util.LinkedHashMap<>(); headerMap.put("foo", ConstantLanguage.constant("ABC")); headerMap.put("bar", ConstantLanguage.constant("XYZ")); diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/setVariables-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/setVariables-eip.adoc index 40c565ae81043..3540038701fcb 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/setVariables-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/setVariables-eip.adoc @@ -152,13 +152,13 @@ XML:: - - ${body} - - - ${variable.foo} > 10 - - + + ${body} + + + ${variable.foo} > 10 + + ---- @@ -195,7 +195,7 @@ If the order in which the variables should be set is important, use a `LinkedHas ._Java-only: using a LinkedHashMap to set variables in order_ [source,java] ---- -private Map variableMap = new java.util.LinkedHashMap<>(); +Map variableMap = new java.util.LinkedHashMap<>(); variableMap.put("foo", ConstantLanguage.constant("ABC")); variableMap.put("bar", ConstantLanguage.constant("XYZ")); diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/split-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/split-eip.adoc index e64c8ef3748e1..df5289871bbb1 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/split-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/split-eip.adoc @@ -77,69 +77,89 @@ YAML:: ---- ==== -The Split EIP has special support for splitting using a delimiter, instead of using -xref:components:languages:tokenize-language.adoc[Tokenize] language. +=== Splitting the message body + +A common use case is to split a list/set/collection/map, array, or something that is iterable +from the message body. -The previous example can also be done as follows: +The Split EIP will by default split the message body based on the value type: + +- `java.util.Collection`: splits by each element from the collection/list/set. +- `java.util.Map`: splits by each `Map.Entry` from the map. +- `Object[]`: splits the array by each element +- `Iterator`: splits by the iterator +- `Iterable`: splits by the iterable +- `org.w3c.dom.NodeList`: splits the XML document by each element from the list +- `String`: splits the string value by comma as separator + +For any other type, the message body is not split, and instead used _as-is_, meaning that +the Split EIP will be split into a single message (the same). + +To use this with the splitter, you should _just_ use body as the expression: [tabs] ==== - Java:: + [source,java] ---- -from("direct:a") - .split(body()).delimiter("\n") - .to("direct:b"); +from("direct:splitUsingBody") + .split(body()) + .to("mock:result"); ---- XML:: + +In XML, you use xref:components:languages:simple-language.adoc[Simple] to refer to the message body: ++ [source,xml] ---- - - - ${body} - + + + ${body} + ---- YAML:: + +In YAML, you use xref:components:languages:simple-language.adoc[Simple] to refer to the message body: ++ [source,yaml] ---- - route: from: - uri: direct:a + uri: direct:splitUsingBody steps: - split: - delimiter: "\n" expression: simple: expression: "${body}" steps: - to: - uri: direct:b + uri: mock:result ---- ==== -The splitter can use any xref:manual:ROOT:expression.adoc[Expression], -so you could use any of the supported languages such as -xref:components:languages:simple-language.adoc[Simple], xref:components:languages:xpath-language.adoc[XPath], -xref:components:languages:jsonpath-language.adoc[JSonPath], xref:components:languages:groovy-language.adoc[Groovy] to perform the split. +=== Splitting with a delimiter + +The Split EIP has special support for splitting using a delimiter, instead of using +xref:components:languages:tokenize-language.adoc[Tokenize] language. + +The first example can also be done as follows: [tabs] ==== + Java:: + [source,java] ---- -from("activemq:my.queue") - .split(xpath("//foo/bar")) - .to("file:some/directory"); +from("direct:a") + .split(body()).delimiter("\n") + .to("direct:b"); ---- XML:: @@ -147,11 +167,11 @@ XML:: [source,xml] ---- - - - //foo/bar - - + + + ${body} + + ---- @@ -161,37 +181,25 @@ YAML:: ---- - route: from: - uri: activemq:my.queue + uri: direct:a steps: - split: + delimiter: "\n" expression: - xpath: - expression: //foo/bar + simple: + expression: "${body}" steps: - to: - uri: file:some/directory + uri: direct:b ---- ==== -=== Splitting the message body - -A common use case is to split a list/set/collection/map, array, or something that is iterable -from the message body. - -The Split EIP will by default split the message body based on the value type: - -- `java.util.Collection`: splits by each element from the collection/list/set. -- `java.util.Map`: splits by each `Map.Entry` from the map. -- `Object[]`: splits the array by each element -- `Iterator`: splits by the iterator -- `Iterable`: splits by the iterable -- `org.w3c.dom.NodeList`: splits the XML document by each element from the list -- `String`: splits the string value by comma as separator - -For any other type, the message body is not split, and instead used _as-is_, meaning that -the Split EIP will be split into a single message (the same). +=== Splitting with an expression -To use this with the splitter, you should _just_ use body as the expression: +The splitter can use any xref:manual:ROOT:expression.adoc[Expression], +so you could use any of the supported languages such as +xref:components:languages:simple-language.adoc[Simple], xref:components:languages:xpath-language.adoc[XPath], +xref:components:languages:jsonpath-language.adoc[JSonPath], xref:components:languages:groovy-language.adoc[Groovy] to perform the split. [tabs] ==== @@ -199,43 +207,39 @@ Java:: + [source,java] ---- -from("direct:splitUsingBody") - .split(body()) - .to("mock:result"); +from("activemq:my.queue") + .split(xpath("//foo/bar")) + .to("file:some/directory"); ---- XML:: + -In XML, you use xref:components:languages:simple-language.adoc[Simple] to refer to the message body: -+ [source,xml] ---- - - - ${body} - - + + + //foo/bar + + ---- YAML:: + -In YAML, you use xref:components:languages:simple-language.adoc[Simple] to refer to the message body: -+ [source,yaml] ---- - route: from: - uri: direct:splitUsingBody + uri: activemq:my.queue steps: - split: expression: - simple: - expression: "${body}" + xpath: + expression: //foo/bar steps: - to: - uri: mock:result + uri: file:some/directory ---- ==== @@ -254,9 +258,9 @@ Java:: ---- from("direct:a") .split(body()).parallelProcessing() - .to("direct:x") - .to("direct:y") - .to("direct:z"); + .to("direct:b") + .to("direct:c") + .to("direct:d"); ---- XML:: @@ -309,7 +313,7 @@ thread that called the splitter, then make sure to enable the synchronous option You may want to continue routing the exchange after the Split EIP. In Java DSL you need to use `end()` to mark where split ends, and where other EIPs can be added to continue the route. -In the example above then sending to mock:result happens after the Split EIP has finished. +In the example below then sending to mock:result happens after the Split EIP has finished. In other words, the message should finish splitting the entire message before the message continues. @@ -321,9 +325,9 @@ Java:: ---- from("direct:a") .split(body()).parallelProcessing() - .to("direct:x") - .to("direct:y") - .to("direct:z") + .to("direct:b") + .to("direct:c") + .to("direct:d") .end() .to("mock:result"); ---- @@ -390,11 +394,11 @@ Java:: + [source,java] ---- -from("direct:start") +from("direct:a") .split(body(), new MyAggregationStrategy()) - .to("direct:x") - .to("direct:y") - .to("direct:z") + .to("direct:b") + .to("direct:c") + .to("direct:d") .end() .to("mock:result"); ---- @@ -515,7 +519,7 @@ Java:: ---- from("direct:streaming") .split(method(new MyCustomSplitter(), "splitMe")).streaming() - .to("activemq:my.parts") + .to("activemq:my.parts"); ---- XML:: @@ -729,7 +733,7 @@ YAML:: ---- ==== -You can set `inheritNamsepaceTagName` property to `*` to include the preceding context in each token (i.e., generating each token enclosed in its ancestor elements). It is noted that each token must share the same ancestor elements in this case. +You can set `inheritNamespaceTagName` property to `*` to include the preceding context in each token (i.e., generating each token enclosed in its ancestor elements). It is noted that each token must share the same ancestor elements in this case. The above tokenizer works well on simple structures but has some inherent limitations in handling more complex XML structures. === Streaming big XML payloads using XML Tokenize language @@ -957,7 +961,7 @@ from("direct:start") // after we have split and handled each message, we want to send a single combined // response back to the original caller, so we let this bean build it for us, // this bean will receive the result of the aggregate strategy: MyOrderStrategy - .to("bean:MyOrderService?method=buildCombinedResponse") + .to("bean:MyOrderService?method=buildCombinedResponse"); ---- XML:: @@ -1541,6 +1545,42 @@ from("direct:line") .to("mock:line"); ---- +XML:: ++ +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + Spring XML:: + [source,xml] @@ -1583,6 +1623,13 @@ YAML:: + [source,yaml] ---- +- errorHandler: + deadLetterChannel: + deadLetterUri: mock:dead + useOriginalMessage: true + redeliveryPolicy: + maximumRedeliveries: 3 + redeliveryDelay: "0" - route: from: uri: direct:start diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc index ce0d869da7445..5e396c9496b38 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/switch-eip.adoc @@ -166,6 +166,58 @@ reference, `ref:department`, to evaluate the message body once and route the res Load it alongside the `direct:billing`, `direct:technical`, and `direct:review` handling routes shown above. +[tabs] +==== +Java:: ++ +[source,java] +---- +import static org.apache.camel.semantic.SemanticEvaluationsBuilder.semanticEvaluations; + +semanticEvaluations(this) + .evaluation("department").expert("decisions") + .type("choice") + .instructions("Which department should handle this message?") + .criterion("billing", "Invoices, payments and refunds") + .criterion("technical", "Bugs, outages and technical problems") + .criterion("other", "Any other request") + .register(); + +from("direct:classify") + .doSwitch().language("semantic", "ref:department") + .doCase("billing", "direct:billing") + .doCase("technical", "direct:technical") + .otherwise("direct:review") + .end(); +---- + +XML:: ++ +[source,xml] +---- + + + + Which department should handle this message? + + + + + + + + + ref:department + + + + + + +---- + +YAML:: ++ [source,yaml] ---- - semantic: @@ -195,6 +247,7 @@ handling routes shown above. otherwise: uri: direct:review ---- +==== == Destinations and management diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc index 1f9fb853484e5..e28f6642e4cf6 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/toD-eip.adoc @@ -426,6 +426,10 @@ Kamelet-style deployments you may want to constrain this to a fixed set of compo option takes a comma-separated allow-list of component schemes; a resolved recipient whose scheme is not in the list is rejected (independently of `ignoreInvalidEndpoint`). By default the option is unset and any scheme is allowed. +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:start") @@ -433,8 +437,8 @@ from("direct:start") .toD().allowedSchemes("http,https").uri("${header.target}"); ---- -And in XML: - +XML:: ++ [source,xml] ---- @@ -443,16 +447,18 @@ And in XML: ---- -And in YAML: - +YAML:: ++ [source,yaml] ---- -- from: - uri: direct:start - steps: - - toD: - uri: "${header.target}" - allowedSchemes: "http,https" +- route: + from: + uri: direct:start + steps: + - toD: + uri: "${header.target}" + allowedSchemes: "http,https" ---- +==== NOTE: `wireTap` extends `toD` and therefore honours the same `allowedSchemes` option. diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/tokenizer-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/tokenizer-eip.adoc index db34196f5afa0..050f8e448224b 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/tokenizer-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/tokenizer-eip.adoc @@ -59,6 +59,44 @@ from("direct:start") .split().body() .to("mock:result"); ---- + +XML:: ++ +[source,xml] +---- + + + + + + + ${body} + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - tokenizer: + langChain4jParagraphTokenizer: + maxTokens: 1024 + maxOverlap: 10 + tokenizerType: OPEN_AI + - split: + expression: + simple: + expression: "${body}" + steps: + - to: + uri: mock:result +---- ==== === Tokenize by tokens @@ -82,6 +120,45 @@ from("direct:start") .split().body() .to("mock:result"); ---- + +XML:: ++ +[source,xml] +---- + + + + + + + ${body} + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - tokenizer: + langChain4jParagraphTokenizer: + maxTokens: 1024 + modelName: gpt-4o-mini + maxOverlap: 10 + tokenizerType: OPEN_AI + - split: + expression: + simple: + expression: "${body}" + steps: + - to: + uri: mock:result +---- ==== == Dependencies diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/transactional-client.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/transactional-client.adoc index 3fc4990987a36..8ec329fb2c668 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/transactional-client.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/transactional-client.adoc @@ -1,4 +1,5 @@ = Transactional Client +:tabs-sync-option: Camel supports the http://www.enterpriseintegrationpatterns.com/TransactionalClient.html[Transactional @@ -225,7 +226,7 @@ Spring XML:: - + ---- ==== @@ -235,31 +236,48 @@ Then we have routes where each of the routes uses their different policy: [tabs] ==== -Spring XML:: +Java:: ++ +[source,java] +---- +from("activemq:queue:inbox") + .transacted("txRequired") + .to("direct:audit") + .to("direct:order") + .to("activemq:queue:order"); + +from("direct:audit") + .transacted("txRequiresNew") + .bean("auditLogService", "insertAuditLog"); + +from("direct:order") + .transacted("txMandatory") + .bean("orderService", "insertOrder"); +---- + +XML:: + [source,xml] ---- - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + ---- YAML:: @@ -315,7 +333,7 @@ The sample is in its full as a https://github.com/apache/camel/blob/main/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/interceptor/TransactionalClientDataSourceWithOnExceptionMinimalConfigurationTest.java[unit test]. First, we set up the usual spring stuff in its configuration file. -Here we have defined a DataSource to the HSQLDB and most +Here we have defined a DataSource to the H2 database and most importantly the Spring `DataSourceTransactionManager` that is doing the heavy lifting of ensuring our transactional policies. @@ -359,45 +377,62 @@ as transacted using the `` XML tag. [tabs] ==== -Spring XML:: +Java:: ++ +[source,java] +---- +from("direct:okay") + // mark this route as transacted + .transacted() + .setBody(constant("Tiger in Action")) + .bean("bookService") + .setBody(constant("Elephant in Action")) + .bean("bookService"); + +from("direct:fail") + // mark this route as transacted + .transacted() + .setBody(constant("Tiger in Action")) + .bean("bookService") + .setBody(constant("Donkey in Action")) + .bean("bookService"); +---- + +XML:: + [source,xml] ---- - - - - - - - - Tiger in Action - - - - Elephant in Action - - - - - - - - - - Tiger in Action - - - - Donkey in Action - - - - - + + + + + + Tiger in Action + + + + Elephant in Action + + + + + + + + + + Tiger in Action + + + + Donkey in Action + + + ---- YAML:: @@ -449,7 +484,7 @@ Remember to use ``. The rest is standard Spring XML to set up the t In this sample, we want to listen for messages in a queue and process the messages with our business logic java code and send them along. Since it is based on a -https://github.com/apache/camel/tree/main/components/camel-jms/src/test/java/org/apache/camel/component/jms/tx/TransactionMinimalConfigurationTest.java[unit test], +https://github.com/apache/camel/blob/main/components/camel-jms/src/test/java/org/apache/camel/component/jms/integration/spring/tx/TransactionMinimalConfigurationIT.java[unit test], the destination is a mock endpoint. First, we configure the standard Spring XML to declare a JMS connection @@ -479,7 +514,7 @@ Spring XML:: - + @@ -497,22 +532,34 @@ route as transacted using the `` XML tag. [tabs] ==== -Spring XML:: +Java:: ++ +[source,java] +---- +// 1: from the jms queue +from("activemq:queue:okay") + // 2: mark this route as transacted + .transacted() + // 3: call our business logic that is myProcessor + .process("myProcessor") + // 4: if success then send it to the mock + .to("mock:result"); +---- + +XML:: + [source,xml] ---- - - - - - - - - - - - - + + + + + + + + + + ---- YAML:: diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc index a1ef60ca72415..c3556e98eaa25 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc @@ -24,7 +24,53 @@ into a Java object; or some other representation according to the data format be == Example -The following example reads XML files from the inbox/xml directory. +The following example reads JSON files from the inbox/json directory. `unmarshal` turns each file into a Map with +xref:dataformats:jackson2-dataformat.adoc[Jackson] (a JSON object becomes a Map, a JSON array a List), so the route can +read its fields, such as `${body[orderId]}`: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("file:inbox/json") + .unmarshal().json(JsonLibrary.Jackson) + .log("Order ${body[orderId]} for customer ${body[customer]}") + .to("bean:validateOrder"); +---- + +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: file:inbox/json + steps: + - unmarshal: + json: + library: Jackson + - log: + message: "Order ${body[orderId]} for customer ${body[customer]}" + - to: + uri: bean:validateOrder +---- +==== + +The next example reads XML files from the inbox/xml directory. Each file is then transformed into Java Objects using xref:dataformats:jaxb-dataformat.adoc[JAXB]. Then a xref:ROOT:bean-component.adoc[Bean] is invoked that takes in the Java object. diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jCharacterTokenizer.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jCharacterTokenizer.json index d968a5b47359b..3baf8ac1bc842 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jCharacterTokenizer.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jCharacterTokenizer.json @@ -17,6 +17,6 @@ "tokenizerType": { "index": 1, "kind": "attribute", "displayName": "Tokenizer Type", "group": "common", "required": true, "type": "enum", "javaType": "org.apache.camel.model.tokenizer.TokenizerType", "enum": [ "OPEN_AI", "AZURE", "QWEN" ], "deprecated": false, "autowired": false, "secret": false, "description": "The tokenizer type. Must be one of OPEN_AI, AZURE or QWEN." }, "maxTokens": { "index": 2, "kind": "attribute", "displayName": "Max Tokens", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens on each segment." }, "maxOverlap": { "index": 3, "kind": "attribute", "displayName": "Max Overlap", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens that can overlap in each segment." }, - "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } + "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jLineTokenizer.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jLineTokenizer.json index 843cf5419594b..364d2590da983 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jLineTokenizer.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jLineTokenizer.json @@ -17,6 +17,6 @@ "tokenizerType": { "index": 1, "kind": "attribute", "displayName": "Tokenizer Type", "group": "common", "required": true, "type": "enum", "javaType": "org.apache.camel.model.tokenizer.TokenizerType", "enum": [ "OPEN_AI", "AZURE", "QWEN" ], "deprecated": false, "autowired": false, "secret": false, "description": "The tokenizer type. Must be one of OPEN_AI, AZURE or QWEN." }, "maxTokens": { "index": 2, "kind": "attribute", "displayName": "Max Tokens", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens on each segment." }, "maxOverlap": { "index": 3, "kind": "attribute", "displayName": "Max Overlap", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens that can overlap in each segment." }, - "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } + "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jParagraphTokenizer.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jParagraphTokenizer.json index 4118c4afeb50c..c252e73498333 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jParagraphTokenizer.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jParagraphTokenizer.json @@ -17,6 +17,6 @@ "tokenizerType": { "index": 1, "kind": "attribute", "displayName": "Tokenizer Type", "group": "common", "required": true, "type": "enum", "javaType": "org.apache.camel.model.tokenizer.TokenizerType", "enum": [ "OPEN_AI", "AZURE", "QWEN" ], "deprecated": false, "autowired": false, "secret": false, "description": "The tokenizer type. Must be one of OPEN_AI, AZURE or QWEN." }, "maxTokens": { "index": 2, "kind": "attribute", "displayName": "Max Tokens", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens on each segment." }, "maxOverlap": { "index": 3, "kind": "attribute", "displayName": "Max Overlap", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens that can overlap in each segment." }, - "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } + "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jSentenceTokenizer.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jSentenceTokenizer.json index 1d8328d7fc916..869f54bc15785 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jSentenceTokenizer.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jSentenceTokenizer.json @@ -17,6 +17,6 @@ "tokenizerType": { "index": 1, "kind": "attribute", "displayName": "Tokenizer Type", "group": "common", "required": true, "type": "enum", "javaType": "org.apache.camel.model.tokenizer.TokenizerType", "enum": [ "OPEN_AI", "AZURE", "QWEN" ], "deprecated": false, "autowired": false, "secret": false, "description": "The tokenizer type. Must be one of OPEN_AI, AZURE or QWEN." }, "maxTokens": { "index": 2, "kind": "attribute", "displayName": "Max Tokens", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens on each segment." }, "maxOverlap": { "index": 3, "kind": "attribute", "displayName": "Max Overlap", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens that can overlap in each segment." }, - "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } + "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jWordTokenizer.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jWordTokenizer.json index 57e842b9d8145..b5d89c3e823b2 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jWordTokenizer.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/langChain4jWordTokenizer.json @@ -17,6 +17,6 @@ "tokenizerType": { "index": 1, "kind": "attribute", "displayName": "Tokenizer Type", "group": "common", "required": true, "type": "enum", "javaType": "org.apache.camel.model.tokenizer.TokenizerType", "enum": [ "OPEN_AI", "AZURE", "QWEN" ], "deprecated": false, "autowired": false, "secret": false, "description": "The tokenizer type. Must be one of OPEN_AI, AZURE or QWEN." }, "maxTokens": { "index": 2, "kind": "attribute", "displayName": "Max Tokens", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens on each segment." }, "maxOverlap": { "index": 3, "kind": "attribute", "displayName": "Max Overlap", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens that can overlap in each segment." }, - "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } + "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd index 278dd6cd0df9f..8edbc97f545fd 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd @@ -14551,7 +14551,7 @@ The maximum number of tokens that can overlap in each segment. - + - + "); + "semantic-language", "", + "switch-eip", ""); private static CamelCatalog catalog; private static Schema springSchema; diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/aggregate-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/aggregate-eip.adoc index db2ec5a92e58e..9a968b7654f7b 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/aggregate-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/aggregate-eip.adoc @@ -114,7 +114,10 @@ be used for creating commonly used aggregation strategies without having to crea In the route below we group all the exchanges together using `GroupedExchangeAggregationStrategy`: -._Java-only: using GroupedExchangeAggregationStrategy_ +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:start") @@ -126,6 +129,44 @@ from("direct:start") .completionTimeout(500L).to("mock:result"); ---- +XML:: ++ +[source,xml] +---- + + + + + true + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - aggregate: + aggregationStrategy: groupedExchangeStrategy + completionTimeout: 500 + correlationExpression: + constant: + expression: "true" + steps: + - to: + uri: mock:result +- beans: + - name: groupedExchangeStrategy + type: org.apache.camel.processor.aggregate.GroupedExchangeAggregationStrategy +---- +==== + As a result we have one outgoing `Exchange` being routed to the `"mock:result"` endpoint. The exchange is a holder containing all the incoming Exchanges. @@ -238,6 +279,61 @@ be used at the same time. Completion is mandatory and must be configured on the aggregation. +For example, to collect the order lines of each order into a List, and send the order on when it has 10 lines, or when +no new line has arrived for 5 seconds: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:lines") + .aggregate(header("orderId"), new GroupedBodyAggregationStrategy()) + .completionSize(10) + .completionTimeout(5000) + .to("direct:order"); +---- + +XML:: ++ +[source,xml] +---- + + + + +
orderId
+
+ +
+
+---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:lines + steps: + - aggregate: + correlationExpression: + header: + expression: orderId + aggregationStrategy: groupedBodies + completionSize: 10 + completionTimeout: 5000 + steps: + - to: + uri: direct:order +- beans: + - name: groupedBodies + type: org.apache.camel.processor.aggregate.GroupedBodyAggregationStrategy +---- +==== + === Pre-completion mode There can be use-cases where you want the incoming @@ -397,7 +493,7 @@ from("direct:start") .aggregate(header("id"), new MyAggregationStrategy()) .completionSize(10).id("myAggregator") .aggregateController(controller) - .to("mock:aggregated"); + .to("mock:result"); ---- XML:: @@ -409,7 +505,7 @@ XML:: -
id
@@ -429,6 +525,7 @@ YAML:: uri: direct:start steps: - aggregate: + id: myAggregator aggregateController: myController aggregationStrategy: myAppender completionSize: 10 @@ -598,9 +695,14 @@ YAML:: ---- ==== -In Java DSL, you can also provide the bean class type directly: +You can also provide the bean class type directly. +In Java DSL you pass the class to `AggregationStrategies.bean`, and in XML or YAML DSL +you specify the bean class in `aggregationStrategy` using the `#class:` syntax as shown: -._Java-only: using AggregationStrategies.bean with class type_ +[tabs] +==== +Java:: ++ [source,java] ---- public void configure() throws Exception { @@ -611,10 +713,49 @@ public void configure() throws Exception { } ---- +XML:: ++ +[source,xml] +---- + + + + + true + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - aggregate: + aggregationStrategy: "#class:com.foo.MyBodyAppender" + aggregationStrategyMethodName: append + completionSize: 3 + correlationExpression: + constant: + expression: "true" + steps: + - to: + uri: mock:result +---- +==== + And if the bean has only one method, we do not need to specify the name of the method: -._Java-only: using AggregationStrategies.bean without method name_ +[tabs] +==== +Java:: ++ [source,java] ---- public void configure() throws Exception { @@ -625,33 +766,13 @@ public void configure() throws Exception { } ---- -And the `append` method could be static: - -._Java-only: bean class with static method_ -[source,java] ----- -public class MyBodyAppender { - - public static String append(String existing, String next) { - return next + existing; - } - -} ----- - -When using XML or YAML DSL, you can also specify the bean class directly in `aggregationStrategy` -using the `#class:` syntax as shown: - -[tabs] -==== - XML:: + [source,xml] ---- - + true @@ -670,7 +791,6 @@ YAML:: steps: - aggregate: aggregationStrategy: "#class:com.foo.MyBodyAppender" - aggregationStrategyMethodName: append completionSize: 3 correlationExpression: constant: @@ -681,17 +801,35 @@ YAML:: ---- ==== +And the `append` method could be static: + +._Java-only: bean class with static method_ +[source,java] +---- +public class MyBodyAppender { + + public static String append(String existing, String next) { + return next + existing; + } + +} +---- + === Aggregating when no data When using bean as `AggregationStrategy`, then the method is *only* invoked when there is data to be aggregated, meaning that the message body is not `null`. In cases where you want to have the method invoked, even when there are no data (message body is `null`), -then set the `strategyMethodAllowNull` to `true`. +then set the `aggregationStrategyMethodAllowNull` to `true`. -When using beans, this can be configured a bit easier using the `beanAllowNull` method -from `AggregationStrategies` as shown: +When using beans in Java DSL, this can be configured a bit easier using the `beanAllowNull` method +from `AggregationStrategies`. In XML and YAML DSL you set the `aggregationStrategyMethodAllowNull` +option to `true`, as shown: -._Java-only: using AggregationStrategies.beanAllowNull_ +[tabs] +==== +Java:: ++ [source,java] ---- public void configure() throws Exception { @@ -701,6 +839,43 @@ public void configure() throws Exception { } ---- +XML:: ++ +[source,xml] +---- + + + + seda:foo + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - pollEnrich: + expression: + constant: + expression: seda:foo + timeout: 1000 + aggregationStrategy: myAppender + aggregationStrategyMethodName: append + aggregationStrategyMethodAllowNull: true + - to: + uri: mock:result +---- +==== + Then the `append` method in the bean would need to deal with the situation that `newExchange` can be `null`: @@ -730,11 +905,21 @@ If we didn't do this, then on timeout the append method would normally not be invoked, meaning the xref:content-enricher.adoc[Content Enricher] did not merge/change the message. -In XML and YAML DSL you would configure the `strategyMethodAllowNull` option and -set it to `true` as shown below: +The same works with the aggregate EIP, as shown below: [tabs] ==== +Java:: ++ +[source,java] +---- +public void configure() throws Exception { + from("direct:start") + .aggregate(constant(true), AggregationStrategies.beanAllowNull(appender, "append")) + .completionSize(3) + .to("mock:result"); +} +---- XML:: + @@ -780,7 +965,7 @@ YAML:: === Aggregating with different body types -When, for example, using `strategyMethodAllowNull` as `true`, then the +When, for example, using `aggregationStrategyMethodAllowNull` as `true`, then the parameter type of the message bodies does not have to be the same. For example suppose we want to aggregate from a `com.foo.User` type to a `List` that contains the name of the user. We could code a bean as follows: diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/cache-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/cache-eip.adoc index b9cca49139629..2a293a62150a7 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/cache-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/cache-eip.adoc @@ -21,6 +21,10 @@ management (bulk ops, invalidation, event listening). The Cache *EIP* covers the most common use case — _"cache the result of this expensive block by key with a TTL"_ — without manual plumbing. It is analogous to `@Cacheable` in Spring: +[tabs] +==== +Java:: ++ [source,java] ---- // Without Cache EIP: 7+ lines of plumbing, tied to one cache technology @@ -29,7 +33,7 @@ from("direct:start") .setHeader("CamelCaffeineKey", header("productId")) .to("caffeine-cache:products") .choice() - .when(body().isNull()) + .when(header("CamelCaffeineActionHasResult").isEqualTo(false)) .to("http://expensive-service") .setHeader("CamelCaffeineAction", constant("PUT")) .to("caffeine-cache:products") @@ -42,6 +46,95 @@ from("direct:start") .end(); ---- +XML:: ++ +[source,xml] +---- + + + + + GET + + +
productId
+
+ + + + ${header.CamelCaffeineActionHasResult} == false + + + PUT + + + + +
+ + + + + + ${header.productId} + + + +---- + +YAML:: ++ +// yaml-validator: skip +[source,yaml] +---- +# Without Cache EIP: manual plumbing, tied to one cache technology +- route: + from: + uri: direct:start + steps: + - setHeader: + name: CamelCaffeineAction + expression: + constant: + expression: GET + - setHeader: + name: CamelCaffeineKey + expression: + header: + expression: productId + - to: + uri: caffeine-cache:products + - choice: + when: + - expression: + simple: + expression: "${header.CamelCaffeineActionHasResult} == false" + steps: + - to: + uri: http://expensive-service + - setHeader: + name: CamelCaffeineAction + expression: + constant: + expression: PUT + - to: + uri: caffeine-cache:products + +# With Cache EIP: backend-agnostic +- route: + from: + uri: direct:start + steps: + - cache: + expression: + simple: + expression: "${header.productId}" + steps: + - to: + uri: http://expensive-service +---- +==== + == Options // eip options: START @@ -60,6 +153,10 @@ Cache errors are handled gracefully: Cache with an auto-created in-memory store (zero config): +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:start") @@ -70,6 +167,45 @@ from("direct:start") .to("direct:continue"); ---- +XML:: ++ +[source,xml] +---- + + + + ${header.productId} + + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - cache: + expression: + simple: + expression: "${header.productId}" + steps: + - to: + uri: http://product-service/api/product + - unmarshal: + json: {} + - to: + uri: direct:continue +---- +==== + Cache with TTL and an explicit `KeyValueRepository`: [tabs] @@ -105,16 +241,21 @@ YAML:: + [source,yaml] ---- -- from: - uri: "direct:start" - steps: - - cache: - simple: "${header.productId}" - keyValueRepository: "myCache" - ttl: "10m" - steps: - - to: "http://product-service/api/product" - - to: "direct:continue" +- route: + from: + uri: direct:start + steps: + - cache: + expression: + simple: + expression: "${header.productId}" + keyValueRepository: myCache + ttl: 10m + steps: + - to: + uri: http://product-service/api/product + - to: + uri: direct:continue ---- ==== @@ -151,6 +292,10 @@ public KeyValueRepository myCache() { Then reference it in the route: +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:start") @@ -160,6 +305,38 @@ from("direct:start") .end(); ---- +XML:: ++ +[source,xml] +---- + + + + ${header.key} + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - cache: + expression: + simple: + expression: "${header.key}" + keyValueRepository: myCache + steps: + - to: + uri: http://service +---- +==== + If a single `KeyValueRepository` bean exists in the registry, it is auto-discovered — no explicit reference needed. NOTE: When no `KeyValueRepository` bean is registered, each `.cache()` block auto-creates its own isolated in-memory @@ -187,6 +364,10 @@ public KeyValueRepository productCache() { } ---- +[tabs] +==== +Java:: ++ [source,java] ---- // Route that caches product lookups @@ -208,8 +389,8 @@ from("jms:topic:product.changes") .log("Invalidated cache for product ${header.productId}"); ---- -=== XML DSL - +XML:: ++ [source,xml] ---- @@ -228,10 +409,45 @@ from("jms:topic:product.changes") ---- +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:getProduct + steps: + - cache: + expression: + simple: + expression: "${header.productId}" + keyValueRepository: productCache + ttl: 30m + steps: + - to: + uri: http://product-service/api/product + +# Invalidation route +- route: + from: + uri: jms:topic:product.changes + steps: + - bean: + ref: productCache + method: "delete(${header.productId})" + - log: + message: "Invalidated cache for product ${header.productId}" +---- +==== + === Clearing the Entire Cache To clear all entries from a `KeyValueRepository`, call `clear()`: +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:clearAll") @@ -241,3 +457,27 @@ from("direct:clearAll") cache.clear(); }); ---- + +XML:: ++ +[source,xml] +---- + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:clearAll + steps: + - bean: + ref: productCache + method: clear +---- +==== diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/choice-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/choice-eip.adoc index c9f0645698c2d..4a42cbb1ef27f 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/choice-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/choice-eip.adoc @@ -220,7 +220,7 @@ The Camel xref:languages:simple-language.adoc[Simple] language is great to use with the Choice EIP in precondition mode to select a specific branch based on xref:manual:ROOT:using-propertyplaceholder.adoc[property placeholders]. -Here we select from the Switch the first predicate that matches. So if there is +Here we select from the Choice the first predicate that matches. So if there is a property placeholder with the key `foo` then it is selected, and so on. Notice how we can use `{{?foo}}` to mark the property placeholder as optional. diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/circuitBreaker-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/circuitBreaker-eip.adoc index 21d3a5f87e5af..afc040ed39e1b 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/circuitBreaker-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/circuitBreaker-eip.adoc @@ -49,38 +49,7 @@ what happened to the call itself: `CamelCircuitBreakerState` is the state of the breaker after the call was recorded, so the call that opens the breaker reports `OPEN` although it was attempted; use the rejected property to know whether a call was made. The caught exception is available in the fallback, for example as `${exception.message}` in the Simple language. - -For example, a fallback that answers differently when the service is down rather than failing once: - -[source,yaml] ----- -- route: - from: - uri: timer:stock?period=1000 - steps: - - circuitBreaker: - steps: - - to: - uri: direct:supplier - onFallback: - steps: - - choice: - when: - - expression: - simple: - expression: "${exchangeProperty.CamelCircuitBreakerResponseRejected} == true" - steps: - - setBody: - expression: - constant: - expression: "supplier is down, using the last known stock" - otherwise: - steps: - - setBody: - expression: - constant: - expression: "stock check failed, will try again" ----- +The example under _Using the exchange properties in the fallback_ below uses these properties. == Example @@ -145,6 +114,89 @@ YAML:: ---- ==== +=== Using the exchange properties in the fallback + +A fallback that answers differently when the service is down rather than failing once: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("timer:stock?period=1000") + .circuitBreaker() + .to("direct:supplier") + .onFallback() + .choice() + .when(simple("${exchangeProperty.CamelCircuitBreakerResponseRejected} == true")) + .setBody(constant("supplier is down, using the last known stock")) + .otherwise() + .setBody(constant("stock check failed, will try again")) + .end() + .end(); +---- + +XML:: ++ +[source,xml] +---- + + + + + + + + ${exchangeProperty.CamelCircuitBreakerResponseRejected} == true + + supplier is down, using the last known stock + + + + + stock check failed, will try again + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: timer:stock?period=1000 + steps: + - circuitBreaker: + steps: + - to: + uri: direct:supplier + onFallback: + steps: + - choice: + when: + - expression: + simple: + expression: "${exchangeProperty.CamelCircuitBreakerResponseRejected} == true" + steps: + - setBody: + expression: + constant: + expression: "supplier is down, using the last known stock" + otherwise: + steps: + - setBody: + expression: + constant: + expression: "stock check failed, will try again" +---- +==== + == Route lifecycle and breaker state When a route is stopped and started, the circuit breaker state is reset (an OPEN breaker returns to CLOSED) diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/claimCheck-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/claimCheck-eip.adoc index da500600b3c0e..4582a9a513089 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/claimCheck-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/claimCheck-eip.adoc @@ -136,104 +136,6 @@ You can also remove headers when merging data back. For example, to remove all h Note you cannot have both include (`+`) and exclude (`-`) `header:pattern` at the same time. -== Dynamic keys - -The claim check keys are static, but you can use the `simple` language syntax to define dynamic keys. -For example, to use a header from the message named `myKey`: - -[tabs] -==== -Java:: -+ -[source,java] ----- -from("direct:start") - .to("mock:a") - .claimCheck(ClaimCheckOperation.Set, "${header.myKey}") - .transform().constant("Bye World") - .to("mock:b") - .claimCheck(ClaimCheckOperation.Get, "${header.myKey}") - .to("mock:c") - .transform().constant("Hi World") - .to("mock:d") - .claimCheck(ClaimCheckOperation.Get, "${header.myKey}") - .to("mock:e"); ----- - -XML:: -+ -[source,xml] ----- - - - - - - Bye World - - - - - - Hi World - - - - - ----- - -YAML:: -+ -[source,yaml] ----- -- route: - from: - uri: direct - parameters: - name: start - steps: - - to: - uri: mock - parameters: - name: a - - claimCheck: - operation: Set - key: "${header.myKey}" - - transform: - expression: - constant: - expression: Bye World - - to: - uri: mock - parameters: - name: b - - claimCheck: - operation: Get - key: "${header.myKey}" - - to: - uri: mock - parameters: - name: c - - transform: - expression: - constant: - expression: Hi World - - to: - uri: mock - parameters: - name: d - - claimCheck: - operation: Get - key: "${header.myKey}" - - to: - uri: mock - parameters: - name: e ----- - -==== - == Example The following example shows the `Push` and `Pop` operations in action: @@ -451,3 +353,101 @@ YAML:: uri: mock:c ---- ==== + +== Dynamic keys + +The claim check keys are static, but you can use the `simple` language syntax to define dynamic keys. +For example, to use a header from the message named `myKey`: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .to("mock:a") + .claimCheck(ClaimCheckOperation.Set, "${header.myKey}") + .transform().constant("Bye World") + .to("mock:b") + .claimCheck(ClaimCheckOperation.Get, "${header.myKey}") + .to("mock:c") + .transform().constant("Hi World") + .to("mock:d") + .claimCheck(ClaimCheckOperation.Get, "${header.myKey}") + .to("mock:e"); +---- + +XML:: ++ +[source,xml] +---- + + + + + + Bye World + + + + + + Hi World + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct + parameters: + name: start + steps: + - to: + uri: mock + parameters: + name: a + - claimCheck: + operation: Set + key: "${header.myKey}" + - transform: + expression: + constant: + expression: Bye World + - to: + uri: mock + parameters: + name: b + - claimCheck: + operation: Get + key: "${header.myKey}" + - to: + uri: mock + parameters: + name: c + - transform: + expression: + constant: + expression: Hi World + - to: + uri: mock + parameters: + name: d + - claimCheck: + operation: Get + key: "${header.myKey}" + - to: + uri: mock + parameters: + name: e +---- + +==== diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/content-enricher.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/content-enricher.adoc index ce81a8f04875a..e652a28767daa 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/content-enricher.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/content-enricher.adoc @@ -108,18 +108,14 @@ YAML:: ---- - route: from: - uri: direct - parameters: - name: start + uri: direct:start steps: - setBody: expression: simple: expression: "${body} World!" - to: - uri: mock - parameters: - name: result + uri: mock:result ---- ==== @@ -188,16 +184,12 @@ YAML:: ---- - route: from: - uri: direct - parameters: - name: start + uri: direct:start steps: - process: ref: myProcessor - to: - uri: mock - parameters: - name: result + uri: mock:result ---- ==== @@ -223,9 +215,9 @@ XML:: [source,xml] ---- - + - + ---- @@ -235,13 +227,13 @@ YAML:: ---- - route: from: - uri: activemq:Input + uri: activemq:My.Queue steps: - bean: ref: myBeanName method: doTransform - to: - uri: activemq:Output + uri: activemq:Another.Queue ---- ==== diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/content-filter-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/content-filter-eip.adoc index 355a53ac163a6..ddaa880c2ba31 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/content-filter-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/content-filter-eip.adoc @@ -15,6 +15,54 @@ image::eip/ContentFilter.gif[image] * Using a xref:manual::processor.adoc[Processor] with the filtering programmed in Java * Using an xref:manual::expression.adoc[Expression] +== Message Content filtering using expression + +Some languages like xref:languages:xpath-language.adoc[XPath], and xref:languages:xquery-language.adoc[XQuery] +can be used to transform and filter content from messages. + +In the example we use xpath to filter a XML message to select all the `` elements: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("activemq:Input") + .setBody().xpath("//foo/bar") + .to("activemq:Output"); +---- + +XML:: ++ +[source,xml] +---- + + + + //foo/bar + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: activemq:Input + steps: + - setBody: + expression: + xpath: + expression: //foo/bar + - to: + uri: activemq:Output +---- +==== + == Message Content filtering using a Processor In this example, we add our own xref:manual::processor.adoc[Processor] using @@ -84,16 +132,12 @@ YAML:: ---- - route: from: - uri: direct - parameters: - name: start + uri: direct:start steps: - process: ref: myProcessor - to: - uri: mock - parameters: - name: result + uri: mock:result ---- ==== @@ -109,9 +153,9 @@ Java:: + [source,java] ---- -from("activemq:My.Queue") +from("activemq:Input") .bean("myBeanName", "doFilter") - .to("activemq:Another.Queue"); + .to("activemq:Output"); ---- XML:: @@ -140,50 +184,3 @@ YAML:: uri: activemq:Output ---- ==== - -== Message Content filtering using expression - -Some languages like xref:languages:xpath-language.adoc[XPath], and xref:languages:xquery-language.adoc[XQuery] -can be used to transform and filter content from messages. - -In the example we use xpath to filter a XML message to select all the `` elements: - -[tabs] -==== -Java:: -+ -[source,java] ----- -from("activemq:Input") - .setBody().xpath("//foo:bar") - .to("activemq:Output"); ----- - -XML:: -+ -[source,xml] ----- - - - - //foo:bar - - - ----- - -YAML:: -+ -[source,yaml] ----- -- route: - from: - uri: activemq:Input - steps: - - setBody: - expression: - xpath: //foo:bar - - to: - uri: activemq:Output ----- -==== diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc index 2df230324a93a..b6148e33bb4b4 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc @@ -61,9 +61,23 @@ Pay attention to Spring XML DSL. The type attribute is used to declare which err And in XML the `` must be configured with an id. The id must then be enabled on either the `` (global scope), or per route that should use this error handler. -The following listing is an equivalent of the prior Java DSL listing: +The following listing shows the error handler together with a route: -._XML-only:_ +[tabs] +==== +Java:: ++ +[source,java] +---- +errorHandler(deadLetterChannel("log:dead?level=ERROR")); + +from("direct:newOrder") + .bean("orderService", "validate") + .bean("orderService", "store"); +---- + +Spring XML:: ++ [source,xml] ---- @@ -77,9 +91,30 @@ The following listing is an equivalent of the prior Java DSL listing: ---- -Notice how we have enabled the error handler on the `` via `errorHandlerRef` +YAML:: ++ +[source,yaml] +---- +- errorHandler: + deadLetterChannel: + deadLetterUri: "log:dead?level=ERROR" +- route: + from: + uri: direct:newOrder + steps: + - bean: + ref: orderService + method: validate + - bean: + ref: orderService + method: store +---- +==== + +Notice how in Spring XML we have enabled the error handler on the `` via `errorHandlerRef` which means this error handler is used globally. You can override this per route, and have individual routes used another error handler. +In Java and YAML, the error handler applies to the routes in the same `RouteBuilder` class or YAML file. NOTE: The DSLs is planned to be improved in the near future to have a unified way of configuring error handling across all DSLs with xref:manual::route-configuration.adoc[Route Configuration]. @@ -264,7 +299,7 @@ Java:: ---- // will use the original message (body and headers) errorHandler(deadLetterChannel("jms:queue:dead") - .useOriginalMessage().maximumRedeliveries(5).redeliveryDelay(5000); + .useOriginalMessage().maximumRedeliveries(5).redeliveryDelay(5000)); ---- Spring XML:: @@ -324,7 +359,7 @@ Java:: ---- errorHandler(deadLetterChannel("jms:queue:dead") .maximumRedeliveries(3) - .onRedelivery(new MyOnRedeliveryProcessor())); + .onRedelivery(new MyRedeliveryProcessor())); ---- Spring XML:: @@ -396,8 +431,7 @@ Configuring this from Spring XML is done with the `onPrepareFailureRef` to refer + [source,xml] ---- - + ---- diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/delay-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/delay-eip.adoc index 53c147d95a68f..ad22500d3731a 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/delay-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/delay-eip.adoc @@ -69,9 +69,9 @@ Note that delay creates its own block, so some DSLs (including Java) require the ._Java-only: closing the delay block with end() in Java DSL_ [source,java] ---- -.from("direct:delayBlockExample") +from("direct:delayBlockExample") .to("direct:getJobState") - .loopDoWhile(simple("${body.state} = 'NOT_DONE'")) + .loopDoWhile(simple("${body.state} == 'NOT_DONE'")) .log(LoggingLevel.DEBUG, "Pausing") .delay(10000) .syncDelayed() diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/idempotentConsumer-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/idempotentConsumer-eip.adoc index 29c614a03d459..d4f2a23cbc95e 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/idempotentConsumer-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/idempotentConsumer-eip.adoc @@ -59,4 +59,140 @@ xref:ROOT:sql-component.adoc[JdbcOrphanLockAwareIdempotentRepository] == Example -For example, see the above implementations for more details. +The idempotent consumer needs an expression that gives the id of each message, and a repository that remembers the ids +it has seen. In the example below the id is the `messageId` header, and the repository is a `MemoryIdempotentRepository`, +which keeps the last 1000 ids in memory. A message with an id that is already in the repository is skipped, so it does +not reach `mock:result`. + +In XML and YAML the repository is a bean, and the idempotent consumer refers to it by name. + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .idempotentConsumer(header("messageId"), MemoryIdempotentRepository.memoryIdempotentRepository()) + .to("mock:result"); +---- + +XML:: ++ +[source,xml] +---- + + + + + +
messageId
+ +
+
+---- + +YAML:: ++ +[source,yaml] +---- +- beans: + - name: myRepo + type: org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository +- route: + from: + uri: direct:start + steps: + - idempotentConsumer: + expression: + header: + expression: messageId + idempotentRepository: myRepo + steps: + - to: + uri: mock:result +---- +==== + +The memory repository forgets the ids when the application stops. To keep them, use `FileIdempotentRepository` or one +of the repositories listed above. + +=== Handling the duplicates in the route + +By default, a duplicate is skipped. When `skipDuplicate` is `false`, a duplicate goes through the idempotent consumer +too, and the exchange property `CamelDuplicateMessage` is `true`. The route can then decide what to do with it, for +example log it: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .idempotentConsumer(header("messageId"), MemoryIdempotentRepository.memoryIdempotentRepository()) + .skipDuplicate(false) + .choice() + .when(simple("${exchangeProperty.CamelDuplicateMessage} == true")) + .log("Message ${header.messageId} is a duplicate") + .otherwise() + .to("mock:result") + .end() + .end(); +---- + +XML:: ++ +[source,xml] +---- + + + + + +
messageId
+ + + ${exchangeProperty.CamelDuplicateMessage} == true + + + + + + +
+
+---- + +YAML:: ++ +[source,yaml] +---- +- beans: + - name: myRepo + type: org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository +- route: + from: + uri: direct:start + steps: + - idempotentConsumer: + expression: + header: + expression: messageId + idempotentRepository: myRepo + skipDuplicate: false + steps: + - choice: + when: + - expression: + simple: + expression: "${exchangeProperty.CamelDuplicateMessage} == true" + steps: + - log: + message: "Message ${header.messageId} is a duplicate" + otherwise: + steps: + - to: + uri: mock:result +---- +==== diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/kamelet-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/kamelet-eip.adoc index 16c97466223d1..d89489cc3fbdc 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/kamelet-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/kamelet-eip.adoc @@ -72,6 +72,7 @@ XML:: YAML:: + +// yaml-validator: skip [source,yaml] ---- - route: diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/log-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/log-eip.adoc index 41980bb8df0eb..faa35aa7cd9ba 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/log-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/log-eip.adoc @@ -276,7 +276,7 @@ Java:: Logger myLogger = org.slf4j.LoggerFactory.getLogger("com.mycompany.mylogger"); from("direct:moo") - .log(myLogger, "Me Got ${body}") + .log(LoggingLevel.INFO, myLogger, "Me Got ${body}") .to("bean:foo"); ---- @@ -284,14 +284,16 @@ XML:: + [source,xml] ---- - - + + + + - + - + ---- @@ -300,7 +302,6 @@ YAML:: [source,yaml] ---- - route: - id: moo from: uri: direct:moo steps: @@ -308,7 +309,13 @@ YAML:: logger: myLogger message: "Me Got ${body}" - to: - uri: mock:baz + uri: bean:foo +- beans: + - name: myLogger + type: org.slf4j.LoggerFactory + factoryMethod: getLogger + constructors: + 0: com.mycompany.mylogger ---- ==== diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/marshal-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/marshal-eip.adoc index 398f85725e39f..5583162409dbd 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/marshal-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/marshal-eip.adoc @@ -24,7 +24,48 @@ into a Java object; or some other representation according to the data format be == Example -The following example reads XML files from the inbox/xml directory. +The following example sends an order as JSON to a JMS queue. The message body is an object, such as a Map or a POJO, +and `marshal` turns it into JSON text with xref:dataformats:jackson2-dataformat.adoc[Jackson]: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:order") + .marshal().json(JsonLibrary.Jackson) + .to("jms:queue:order"); +---- + +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:order + steps: + - marshal: + json: + library: Jackson + - to: + uri: jms:queue:order +---- +==== + +The next example reads XML files from the inbox/xml directory. Each file is then transformed into Java Objects using xref:dataformats:jaxb-dataformat.adoc[JAXB]. Then a xref:ROOT:bean-component.adoc[Bean] is invoked that takes in the Java object. diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/pausable-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/pausable-eip.adoc index 20bc377177a84..cdbacc58be292 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/pausable-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/pausable-eip.adoc @@ -33,11 +33,49 @@ from("kafka:topic") .process(exchange -> LOG.info("Received: {}", exchange.getMessage().getBody())) .to("direct:destination"); ---- + +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: kafka:topic + steps: + - pausable: + consumerListener: kafkaConsumerListener + untilCheck: canContinue + - log: + message: "Received: ${body}" + - to: + uri: direct:destination +- beans: + - name: kafkaConsumerListener + type: org.apache.camel.component.kafka.consumer.errorhandler.KafkaConsumerListener + - name: canContinue + type: com.foo.CanContinuePredicate +---- ==== You can also integrate the Pausable EIP with the xref:circuitBreaker-eip.adoc[Circuit Breaker EIP] to pause consumption based on downstream system availability: +[tabs] +==== +Java:: ++ [source,java] ---- from("kafka:topic") @@ -48,6 +86,40 @@ from("kafka:topic") .end(); ---- +XML:: ++ +[source,xml] +---- + + + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: kafka:topic + steps: + - pausable: + consumerListener: kafkaConsumerListener + untilCheck: canContinue + - circuitBreaker: + resilience4jConfiguration: + circuitBreaker: pausableCircuit + steps: + - to: + uri: direct:downstream +---- +==== + == See Also * xref:resume-strategies.adoc[Resume Strategies] diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/pollEnrich-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/pollEnrich-eip.adoc index 3dfccbcd508e0..2ad4c0890e0a9 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/pollEnrich-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/pollEnrich-eip.adoc @@ -74,6 +74,100 @@ You can pass in a timeout value that determines which method to use: The timeout values are in milliseconds. +For example, to read the file `data.txt` from the inbox directory, waiting up to 10 seconds for it. Without an +`AggregationStrategy`, the polled message is the result, so the content of the file becomes the message body: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .pollEnrich("file:inbox?fileName=data.txt", 10000) + .to("mock:result"); +---- + +XML:: ++ +[source,xml] +---- + + + + file:inbox?fileName=data.txt + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - pollEnrich: + timeout: 10000 + expression: + constant: + expression: file:inbox?fileName=data.txt + - to: + uri: mock:result +---- +==== + +=== Poll Enrich with Dynamic Endpoints + +Both `enrich` and `pollEnrich` supports using dynamic uris computed based on information from the current `Exchange`. + +For example to `pollEnrich` from an endpoint that uses a header to indicate a SEDA queue name: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:start") + .pollEnrich().simple("seda:${header.queueName}") + .to("direct:result"); +---- + +XML:: ++ +[source,xml] +---- + + + + seda:${header.queueName} + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - pollEnrich: + expression: + simple: + expression: "seda:${header.queueName}" + - to: + uri: direct:result +---- +==== + +TIP: See the `cacheSize` option for more details on _how much cache_ to use depending on how many or few unique endpoints are used. + === Using Poll Enrich The content enricher (`pollEnrich`) retrieves additional data from a _resource endpoint_ in order to enrich an incoming message (contained in the _original exchange_). @@ -218,55 +312,6 @@ YAML:: Notice that the enriched endpoint is a constant, however, Camel also supports dynamic endpoints which is covered next. -=== Poll Enrich with Dynamic Endpoints - -Both `enrich` and `pollEnrich` supports using dynamic uris computed based on information from the current `Exchange`. - -For example to `pollEnrich` from an endpoint that uses a header to indicate a SEDA queue name: - -[tabs] -==== -Java:: -+ -[source,java] ----- -from("direct:start") - .pollEnrich().simple("seda:${header.queueName}") - .to("direct:result"); ----- - -XML:: -+ -[source,xml] ----- - - - - seda:${header.queueName} - - - ----- - -YAML:: -+ -[source,yaml] ----- -- route: - from: - uri: direct:start - steps: - - pollEnrich: - expression: - simple: - expression: "seda:${header.queueName}" - - to: - uri: direct:result ----- -==== - -TIP: See the `cacheSize` option for more details on _how much cache_ to use depending on how many or few unique endpoints are used. - === Using Poll Enrich with file based components When using `poll` or `pollEnrich` with the file based components, then the `eagerLimitMaxMessagesPerPoll` option diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/polling-consumer.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/polling-consumer.adoc index d06b471755975..152e13341cbb5 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/polling-consumer.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/polling-consumer.adoc @@ -44,6 +44,7 @@ You can programmatically create an instance of `PollingConsumer` from any endpoi ---- Endpoint endpoint = context.getEndpoint("activemq:my.queue"); PollingConsumer consumer = endpoint.createPollingConsumer(); +consumer.start(); Exchange exchange = consumer.receive(); ---- @@ -96,6 +97,7 @@ as shown below: Endpoint endpoint = context.getEndpoint("file:inbox?pollingConsumerQueueSize=50"); PollingConsumer consumer = endpoint.createPollingConsumer(); +consumer.start(); Exchange exchange = consumer.receive(5000); ---- diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/process-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/process-eip.adoc index 654b09187ac4b..d4acf724f9f2f 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/process-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/process-eip.adoc @@ -66,13 +66,44 @@ public class MyProcessor implements Processor { Then in Camel you can call this processor: -._Java-only: calling a Processor by instantiating it directly_ +[tabs] +==== +Java:: ++ [source,java] ---- from("activemq:myQueue") .process(new MyProcessor()); ---- +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: activemq:myQueue + steps: + - process: + ref: myProcessor +- beans: + - name: myProcessor + type: com.foo.MyProcessor +---- +==== + You can also call a processor by its bean id, if the processor has been enlisted in the xref:manual::registry.adoc[Registry], such as with the id `myProcessor`: diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/recipientList-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/recipientList-eip.adoc index f0efa65b63678..5a734e2c8e7a5 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/recipientList-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/recipientList-eip.adoc @@ -143,10 +143,23 @@ The dynamic list of recipients that are defined in the header must be iterable s === Configuring delimiter for dynamic destinations -In XML DSL you can set the delimiter attribute for setting a delimiter to be used if the header value is a single `String` with multiple separated endpoints. +You can set the `delimiter` option for setting a delimiter to be used if the header value is a single `String` with multiple separated endpoints. By default, Camel uses comma as delimiter, but this option lets you specify a custom delimiter to use instead. -._XML-only:_ +[tabs] +==== +Java:: ++ +In Java DSL, you specify the delimiter as second parameter: ++ +[source,java] +---- +from("direct:a") + .recipientList(header("myHeader"), ";"); +---- + +XML:: ++ [source,xml] ---- @@ -158,8 +171,8 @@ By default, Camel uses comma as delimiter, but this option lets you specify a cu ---- -And in YAML DSL you set the `delimiter` parameter: - +YAML:: ++ [source,yaml] ---- - route: @@ -172,21 +185,13 @@ And in YAML DSL you set the `delimiter` parameter: header: expression: myHeader ---- +==== So if *myHeader* contains a `String` with the value `"activemq:queue:foo;activemq:topic:hello ; log:bar"` -then Camel will split the `String` using the delimiter given in the XML that was comma, +then Camel will split the `String` using the delimiter, which is a semicolon, resulting into three endpoints to send to. You can use spaces between the endpoints as Camel will trim the value when it looks up the endpoint to send to. -In Java DSL, you specify the delimiter as second parameter as shown below: - -._Java-only: the delimiter is passed as a second parameter to recipientList_ -[source,java] ----- -from("direct:a") - .recipientList(header("myHeader"), ";"); ----- - === Using parallel processing The Recipient List supports `parallelProcessing` similar to what xref:multicast-eip.adoc[Multicast] and xref:split-eip.adoc[Split] EIPs have as well. @@ -415,10 +420,13 @@ So use this option with a bit of care. ==== For example, in the unit test below, you can see that we multicast the message to three destinations. -We have a timeout of 2 seconds, which means only the last two messages can be completed within the timeframe. +We have a timeout of 250 milliseconds, which means only the last two messages can be completed within the timeframe. This means we will only aggregate the last two which yields a result aggregation which outputs "BC". -._Java-only: multicast with inline AggregationStrategy and timeout_ +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:start") @@ -445,6 +453,111 @@ from("direct:b").to("mock:B").setBody(constant("B")); from("direct:c").to("mock:C").setBody(constant("C")); ---- +XML:: ++ +[source,xml] +---- + + + + + + + + + + + + + + + 1000 + + + + A + + + + + + + + B + + + + + + + + C + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - multicast: + aggregationStrategy: myStrategy + parallelProcessing: true + timeout: 250 + steps: + - to: + uri: direct:a + - to: + uri: direct:b + - to: + uri: direct:c + - to: + uri: mock:result +- route: + from: + uri: direct:a + steps: + - delay: + expression: + constant: + expression: "1000" + - to: + uri: mock:A + - setBody: + expression: + constant: + expression: A +- route: + from: + uri: direct:b + steps: + - to: + uri: mock:B + - setBody: + expression: + constant: + expression: B +- route: + from: + uri: direct:c + steps: + - to: + uri: mock:C + - setBody: + expression: + constant: + expression: C +- beans: + - name: myStrategy + type: com.foo.MyAggregationStrategy +---- +==== + By default, if a timeout occurs the `AggregationStrategy` is not invoked. However, you can implement the `timeout` method: This allows you to deal with the timeout in the `AggregationStrategy` if you really need to. diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/resumable-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/resumable-eip.adoc index cd2c2c679afbe..b1a8191f2aa73 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/resumable-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/resumable-eip.adoc @@ -37,12 +37,45 @@ from("some:component") .resumable().configuration(kafkaConfigurationBuilder) .process(this::process); ---- + +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: some:component + steps: + - resumable: + resumeStrategy: kafkaResumeStrategy + - process: + ref: myProcessor +---- ==== +NOTE: The configuration builder is only available in the Java DSL. +In XML and YAML, the `resumeStrategy` option refers to a resume strategy bean by its name. +In this example, `kafkaResumeStrategy` is a `SingleNodeKafkaResumeStrategy` bean set up with the same configuration. + === Intermittent Mode Enable intermittent mode to avoid updating the offset for every exchange: +[tabs] +==== +Java:: ++ [source,java] ---- from("some:component") @@ -50,6 +83,36 @@ from("some:component") .process(this::process); ---- +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: some:component + steps: + - resumable: + resumeStrategy: testResumeStrategy + intermittent: true + - process: + ref: myProcessor +- beans: + - name: testResumeStrategy + type: com.foo.MyTestResumeStrategy +---- +==== + == See Also * xref:resume-strategies.adoc[Resume Strategies] diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/resume-strategies.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/resume-strategies.adoc index 569e0b61619e9..a29e649658209 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/resume-strategies.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/resume-strategies.adoc @@ -4,6 +4,7 @@ :description: Provide strategies to allow consuming data from specific offsets :since: 3.16.0 :supportlevel: Experimental +:tabs-sync-option: The resume strategies allow users to implement strategies that point the consumer part of the routes to the last point of consumption. This allows Camel to skip reading and processing data that has already been consumed. @@ -35,7 +36,7 @@ The resume strategies comes in three parts: == The DSL method The route needs to use the `resumable()` method followed by passing a strategy configuration using `configuration`. -It is also possible to use the `resumableStrategy` to point to an instance of the resume strategy in use, although +It is also possible to use the `resumeStrategy` to point to an instance of the resume strategy in use, although this is much more complex. The vast majority of the cases should use a `configuration`, in which case Camel will do the heavy-lifting for you. @@ -60,7 +61,10 @@ from("some:component") This instance can be bound in the Context registry as follows: -._Java-only: binding resume strategy to the registry_ +[tabs] +==== +Java:: ++ [source,java] ---- getCamelContext().getRegistry().bind("testResumeStrategy", new MyTestResumeStrategy()); @@ -71,17 +75,97 @@ from("some:component") .process(this::process); ---- +XML:: ++ +[source,xml] +---- + + + + + + + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- beans: + - name: testResumeStrategy + type: com.foo.MyTestResumeStrategy + - name: resumeCache + type: com.foo.MyChoiceOfResumeCache + constructors: + 0: 100 +- route: + from: + uri: some:component + steps: + - resumable: + resumeStrategy: testResumeStrategy + - process: + ref: myProcessor +---- +==== + Or the instance can be constructed as follows: -._Java-only: constructing resume strategy inline_ +[tabs] +==== +Java:: ++ [source,java] ---- getCamelContext().getRegistry().bind("resumeCache", new MyChoiceOfResumeCache<>(100)); from("some:component") .resumable(new MyTestResumeStrategy()) - .process(this::process) + .process(this::process); +---- + +XML:: ++ +[source,xml] +---- + + + + + + + + + + + +---- + +YAML:: ++ +[source,yaml] ---- +- beans: + - name: resumeCache + type: com.foo.MyChoiceOfResumeCache + constructors: + 0: 100 +- route: + from: + uri: some:component + steps: + - resumable: + resumeStrategy: "#class:com.foo.MyTestResumeStrategy" + - process: + ref: myProcessor +---- +==== In some circumstances, such as when dealing with File I/O, it may be necessary to set the offset manually. There are *supporting classes* that can help work with resumables: @@ -94,13 +178,46 @@ In some circumstances, such as when dealing with File I/O, it may be necessary t In some cases, it may be necessary to avoid updating the offset for every exchange. You can enable the intermittent mode to modify the route behavior so that missing offsets will not cause an exception: -._Java-only: enabling intermittent mode on resumable_ +[tabs] +==== +Java:: ++ [source,java] ---- from("some:component") -.resumable().resumeStrategy(new MyTestResumeStrategy()).intermittent(true) -.process(this::process) + .resumable().resumeStrategy(new MyTestResumeStrategy()).intermittent(true) + .process(this::process); +---- + +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: some:component + steps: + - resumable: + resumeStrategy: testResumeStrategy + intermittent: true + - process: + ref: myProcessor +- beans: + - name: testResumeStrategy + type: com.foo.MyTestResumeStrategy ---- +==== == Builtin Resume Strategies @@ -140,7 +257,10 @@ When using the converters with the file component, beware of the differences in For instance, the behavior of: -._Java-only: resumable with Reader body type_ +[tabs] +==== +Java:: ++ [source,java] ---- from("file:{{input.dir}}?noop=true&fileName={{input.file}}") @@ -149,9 +269,44 @@ from("file:{{input.dir}}?noop=true&fileName={{input.file}}") .process(this::process); ---- +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: "file:{{input.dir}}" + parameters: + noop: true + fileName: "{{input.file}}" + steps: + - resumable: + resumeStrategy: testResumeStrategy + - convertBodyTo: + type: java.io.Reader + - process: + ref: myProcessor +---- +==== + It is different from the behavior of: -._Java-only: resumable with InputStream body type_ +[tabs] +==== +Java:: ++ [source,java] ---- from("file:{{input.dir}}?noop=true&fileName={{input.file}}") @@ -160,6 +315,38 @@ from("file:{{input.dir}}?noop=true&fileName={{input.file}}") .process(this::process); ---- +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: "file:{{input.dir}}" + parameters: + noop: true + fileName: "{{input.file}}" + steps: + - resumable: + resumeStrategy: testResumeStrategy + - convertBodyTo: + type: java.io.InputStream + - process: + ref: myProcessor +---- +==== + *Reason*: the `skip` method in the Reader will skip characters, whereas the same method on the InputStream will skip bytes. @@ -180,14 +367,52 @@ To use the API, it needs an instance of a Consumer listener along with a predica Usage example: -._Java-only: using pausable consumer API_ +[tabs] +==== +Java:: ++ [source,java] ---- -from(from) +from("kafka:topic") .pausable(new KafkaConsumerListener(), o -> canContinue()) .process(exchange -> LOG.info("Received an exchange: {}", exchange.getMessage().getBody())) - .to(destination); + .to("direct:destination"); +---- + +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: kafka:topic + steps: + - pausable: + consumerListener: kafkaConsumerListener + untilCheck: canContinue + - log: + message: "Received an exchange: ${body}" + - to: + uri: direct:destination +- beans: + - name: kafkaConsumerListener + type: org.apache.camel.component.kafka.consumer.errorhandler.KafkaConsumerListener + - name: canContinue + type: com.foo.CanContinuePredicate ---- +==== You can also integrate the pausable API and the consumer listener with the circuit breaker EIP. For instance, it's possible to configure the circuit breaker so that it can manipulate the state of the listener based on success or on @@ -199,7 +424,7 @@ On success, it shuts down the scheduled check. An example implementation of this approach would be similar to this: -._Java-only: integrating pausable consumer with circuit breaker_ +._Java-only: creating the circuit breaker and binding it to the registry_ [source,java] ---- CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("pausable"); @@ -218,21 +443,68 @@ circuitBreaker.getEventPublisher() if (executorService == null) { executorService = Executors.newSingleThreadScheduledExecutor(); - // In a real world scenario, instead of incrementing, it could be pinging a remote system or - // running a similar check to determine whether it's available. That + // In a real world scenario, it could be pinging a remote system or + // running a similar check to determine whether it's available. executorService.scheduleAtFixedRate(() -> someCheckMethod(), 1, 1, TimeUnit.SECONDS); } }); // Binds the configuration to the registry - getCamelContext().getRegistry().bind("pausableCircuit", circuitBreaker); +getCamelContext().getRegistry().bind("pausableCircuit", circuitBreaker); +---- -from(from) +The route then uses the circuit breaker by its name in the registry, `pausableCircuit`: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("kafka:topic") .pausable(new KafkaConsumerListener(), o -> canContinue()) .routeId("pausable-it") .process(exchange -> LOG.info("Got record from Kafka: {}", exchange.getMessage().getBody())) .circuitBreaker() .resilience4jConfiguration().circuitBreaker("pausableCircuit").end() - .to(to) + .to("direct:downstream") .end(); ---- + +XML:: ++ +[source,xml] +---- + + + + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + id: pausable-it + from: + uri: kafka:topic + steps: + - pausable: + consumerListener: kafkaConsumerListener + untilCheck: canContinue + - log: + message: "Got record from Kafka: ${body}" + - circuitBreaker: + resilience4jConfiguration: + circuitBreaker: pausableCircuit + steps: + - to: + uri: direct:downstream +---- +==== diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/setHeaders-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/setHeaders-eip.adoc index 2be3b1e8be27d..75cff6e97242e 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/setHeaders-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/setHeaders-eip.adoc @@ -152,13 +152,13 @@ XML:: - - ${body} - - - ${header.foo} > 10 - - + + ${body} + + + ${header.foo} > 10 + + ---- @@ -195,7 +195,7 @@ If the order in which the headers should be set is important, use a `LinkedHashM ._Java-only: using a LinkedHashMap to set headers in order_ [source,java] ---- -private Map headerMap = new java.util.LinkedHashMap<>(); +Map headerMap = new java.util.LinkedHashMap<>(); headerMap.put("foo", ConstantLanguage.constant("ABC")); headerMap.put("bar", ConstantLanguage.constant("XYZ")); diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/setVariables-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/setVariables-eip.adoc index 40c565ae81043..3540038701fcb 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/setVariables-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/setVariables-eip.adoc @@ -152,13 +152,13 @@ XML:: - - ${body} - - - ${variable.foo} > 10 - - + + ${body} + + + ${variable.foo} > 10 + + ---- @@ -195,7 +195,7 @@ If the order in which the variables should be set is important, use a `LinkedHas ._Java-only: using a LinkedHashMap to set variables in order_ [source,java] ---- -private Map variableMap = new java.util.LinkedHashMap<>(); +Map variableMap = new java.util.LinkedHashMap<>(); variableMap.put("foo", ConstantLanguage.constant("ABC")); variableMap.put("bar", ConstantLanguage.constant("XYZ")); diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/split-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/split-eip.adoc index e64c8ef3748e1..df5289871bbb1 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/split-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/split-eip.adoc @@ -77,69 +77,89 @@ YAML:: ---- ==== -The Split EIP has special support for splitting using a delimiter, instead of using -xref:components:languages:tokenize-language.adoc[Tokenize] language. +=== Splitting the message body + +A common use case is to split a list/set/collection/map, array, or something that is iterable +from the message body. -The previous example can also be done as follows: +The Split EIP will by default split the message body based on the value type: + +- `java.util.Collection`: splits by each element from the collection/list/set. +- `java.util.Map`: splits by each `Map.Entry` from the map. +- `Object[]`: splits the array by each element +- `Iterator`: splits by the iterator +- `Iterable`: splits by the iterable +- `org.w3c.dom.NodeList`: splits the XML document by each element from the list +- `String`: splits the string value by comma as separator + +For any other type, the message body is not split, and instead used _as-is_, meaning that +the Split EIP will be split into a single message (the same). + +To use this with the splitter, you should _just_ use body as the expression: [tabs] ==== - Java:: + [source,java] ---- -from("direct:a") - .split(body()).delimiter("\n") - .to("direct:b"); +from("direct:splitUsingBody") + .split(body()) + .to("mock:result"); ---- XML:: + +In XML, you use xref:components:languages:simple-language.adoc[Simple] to refer to the message body: ++ [source,xml] ---- - - - ${body} - + + + ${body} + ---- YAML:: + +In YAML, you use xref:components:languages:simple-language.adoc[Simple] to refer to the message body: ++ [source,yaml] ---- - route: from: - uri: direct:a + uri: direct:splitUsingBody steps: - split: - delimiter: "\n" expression: simple: expression: "${body}" steps: - to: - uri: direct:b + uri: mock:result ---- ==== -The splitter can use any xref:manual:ROOT:expression.adoc[Expression], -so you could use any of the supported languages such as -xref:components:languages:simple-language.adoc[Simple], xref:components:languages:xpath-language.adoc[XPath], -xref:components:languages:jsonpath-language.adoc[JSonPath], xref:components:languages:groovy-language.adoc[Groovy] to perform the split. +=== Splitting with a delimiter + +The Split EIP has special support for splitting using a delimiter, instead of using +xref:components:languages:tokenize-language.adoc[Tokenize] language. + +The first example can also be done as follows: [tabs] ==== + Java:: + [source,java] ---- -from("activemq:my.queue") - .split(xpath("//foo/bar")) - .to("file:some/directory"); +from("direct:a") + .split(body()).delimiter("\n") + .to("direct:b"); ---- XML:: @@ -147,11 +167,11 @@ XML:: [source,xml] ---- - - - //foo/bar - - + + + ${body} + + ---- @@ -161,37 +181,25 @@ YAML:: ---- - route: from: - uri: activemq:my.queue + uri: direct:a steps: - split: + delimiter: "\n" expression: - xpath: - expression: //foo/bar + simple: + expression: "${body}" steps: - to: - uri: file:some/directory + uri: direct:b ---- ==== -=== Splitting the message body - -A common use case is to split a list/set/collection/map, array, or something that is iterable -from the message body. - -The Split EIP will by default split the message body based on the value type: - -- `java.util.Collection`: splits by each element from the collection/list/set. -- `java.util.Map`: splits by each `Map.Entry` from the map. -- `Object[]`: splits the array by each element -- `Iterator`: splits by the iterator -- `Iterable`: splits by the iterable -- `org.w3c.dom.NodeList`: splits the XML document by each element from the list -- `String`: splits the string value by comma as separator - -For any other type, the message body is not split, and instead used _as-is_, meaning that -the Split EIP will be split into a single message (the same). +=== Splitting with an expression -To use this with the splitter, you should _just_ use body as the expression: +The splitter can use any xref:manual:ROOT:expression.adoc[Expression], +so you could use any of the supported languages such as +xref:components:languages:simple-language.adoc[Simple], xref:components:languages:xpath-language.adoc[XPath], +xref:components:languages:jsonpath-language.adoc[JSonPath], xref:components:languages:groovy-language.adoc[Groovy] to perform the split. [tabs] ==== @@ -199,43 +207,39 @@ Java:: + [source,java] ---- -from("direct:splitUsingBody") - .split(body()) - .to("mock:result"); +from("activemq:my.queue") + .split(xpath("//foo/bar")) + .to("file:some/directory"); ---- XML:: + -In XML, you use xref:components:languages:simple-language.adoc[Simple] to refer to the message body: -+ [source,xml] ---- - - - ${body} - - + + + //foo/bar + + ---- YAML:: + -In YAML, you use xref:components:languages:simple-language.adoc[Simple] to refer to the message body: -+ [source,yaml] ---- - route: from: - uri: direct:splitUsingBody + uri: activemq:my.queue steps: - split: expression: - simple: - expression: "${body}" + xpath: + expression: //foo/bar steps: - to: - uri: mock:result + uri: file:some/directory ---- ==== @@ -254,9 +258,9 @@ Java:: ---- from("direct:a") .split(body()).parallelProcessing() - .to("direct:x") - .to("direct:y") - .to("direct:z"); + .to("direct:b") + .to("direct:c") + .to("direct:d"); ---- XML:: @@ -309,7 +313,7 @@ thread that called the splitter, then make sure to enable the synchronous option You may want to continue routing the exchange after the Split EIP. In Java DSL you need to use `end()` to mark where split ends, and where other EIPs can be added to continue the route. -In the example above then sending to mock:result happens after the Split EIP has finished. +In the example below then sending to mock:result happens after the Split EIP has finished. In other words, the message should finish splitting the entire message before the message continues. @@ -321,9 +325,9 @@ Java:: ---- from("direct:a") .split(body()).parallelProcessing() - .to("direct:x") - .to("direct:y") - .to("direct:z") + .to("direct:b") + .to("direct:c") + .to("direct:d") .end() .to("mock:result"); ---- @@ -390,11 +394,11 @@ Java:: + [source,java] ---- -from("direct:start") +from("direct:a") .split(body(), new MyAggregationStrategy()) - .to("direct:x") - .to("direct:y") - .to("direct:z") + .to("direct:b") + .to("direct:c") + .to("direct:d") .end() .to("mock:result"); ---- @@ -515,7 +519,7 @@ Java:: ---- from("direct:streaming") .split(method(new MyCustomSplitter(), "splitMe")).streaming() - .to("activemq:my.parts") + .to("activemq:my.parts"); ---- XML:: @@ -729,7 +733,7 @@ YAML:: ---- ==== -You can set `inheritNamsepaceTagName` property to `*` to include the preceding context in each token (i.e., generating each token enclosed in its ancestor elements). It is noted that each token must share the same ancestor elements in this case. +You can set `inheritNamespaceTagName` property to `*` to include the preceding context in each token (i.e., generating each token enclosed in its ancestor elements). It is noted that each token must share the same ancestor elements in this case. The above tokenizer works well on simple structures but has some inherent limitations in handling more complex XML structures. === Streaming big XML payloads using XML Tokenize language @@ -957,7 +961,7 @@ from("direct:start") // after we have split and handled each message, we want to send a single combined // response back to the original caller, so we let this bean build it for us, // this bean will receive the result of the aggregate strategy: MyOrderStrategy - .to("bean:MyOrderService?method=buildCombinedResponse") + .to("bean:MyOrderService?method=buildCombinedResponse"); ---- XML:: @@ -1541,6 +1545,42 @@ from("direct:line") .to("mock:line"); ---- +XML:: ++ +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + Spring XML:: + [source,xml] @@ -1583,6 +1623,13 @@ YAML:: + [source,yaml] ---- +- errorHandler: + deadLetterChannel: + deadLetterUri: mock:dead + useOriginalMessage: true + redeliveryPolicy: + maximumRedeliveries: 3 + redeliveryDelay: "0" - route: from: uri: direct:start diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc index ce0d869da7445..5e396c9496b38 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/switch-eip.adoc @@ -166,6 +166,58 @@ reference, `ref:department`, to evaluate the message body once and route the res Load it alongside the `direct:billing`, `direct:technical`, and `direct:review` handling routes shown above. +[tabs] +==== +Java:: ++ +[source,java] +---- +import static org.apache.camel.semantic.SemanticEvaluationsBuilder.semanticEvaluations; + +semanticEvaluations(this) + .evaluation("department").expert("decisions") + .type("choice") + .instructions("Which department should handle this message?") + .criterion("billing", "Invoices, payments and refunds") + .criterion("technical", "Bugs, outages and technical problems") + .criterion("other", "Any other request") + .register(); + +from("direct:classify") + .doSwitch().language("semantic", "ref:department") + .doCase("billing", "direct:billing") + .doCase("technical", "direct:technical") + .otherwise("direct:review") + .end(); +---- + +XML:: ++ +[source,xml] +---- + + + + Which department should handle this message? + + + + + + + + + ref:department + + + + + + +---- + +YAML:: ++ [source,yaml] ---- - semantic: @@ -195,6 +247,7 @@ handling routes shown above. otherwise: uri: direct:review ---- +==== == Destinations and management diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc index 1f9fb853484e5..e28f6642e4cf6 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/toD-eip.adoc @@ -426,6 +426,10 @@ Kamelet-style deployments you may want to constrain this to a fixed set of compo option takes a comma-separated allow-list of component schemes; a resolved recipient whose scheme is not in the list is rejected (independently of `ignoreInvalidEndpoint`). By default the option is unset and any scheme is allowed. +[tabs] +==== +Java:: ++ [source,java] ---- from("direct:start") @@ -433,8 +437,8 @@ from("direct:start") .toD().allowedSchemes("http,https").uri("${header.target}"); ---- -And in XML: - +XML:: ++ [source,xml] ---- @@ -443,16 +447,18 @@ And in XML: ---- -And in YAML: - +YAML:: ++ [source,yaml] ---- -- from: - uri: direct:start - steps: - - toD: - uri: "${header.target}" - allowedSchemes: "http,https" +- route: + from: + uri: direct:start + steps: + - toD: + uri: "${header.target}" + allowedSchemes: "http,https" ---- +==== NOTE: `wireTap` extends `toD` and therefore honours the same `allowedSchemes` option. diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/tokenizer-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/tokenizer-eip.adoc index db34196f5afa0..050f8e448224b 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/tokenizer-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/tokenizer-eip.adoc @@ -59,6 +59,44 @@ from("direct:start") .split().body() .to("mock:result"); ---- + +XML:: ++ +[source,xml] +---- + + + + + + + ${body} + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - tokenizer: + langChain4jParagraphTokenizer: + maxTokens: 1024 + maxOverlap: 10 + tokenizerType: OPEN_AI + - split: + expression: + simple: + expression: "${body}" + steps: + - to: + uri: mock:result +---- ==== === Tokenize by tokens @@ -82,6 +120,45 @@ from("direct:start") .split().body() .to("mock:result"); ---- + +XML:: ++ +[source,xml] +---- + + + + + + + ${body} + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:start + steps: + - tokenizer: + langChain4jParagraphTokenizer: + maxTokens: 1024 + modelName: gpt-4o-mini + maxOverlap: 10 + tokenizerType: OPEN_AI + - split: + expression: + simple: + expression: "${body}" + steps: + - to: + uri: mock:result +---- ==== == Dependencies diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/transactional-client.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/transactional-client.adoc index 3fc4990987a36..8ec329fb2c668 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/transactional-client.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/transactional-client.adoc @@ -1,4 +1,5 @@ = Transactional Client +:tabs-sync-option: Camel supports the http://www.enterpriseintegrationpatterns.com/TransactionalClient.html[Transactional @@ -225,7 +226,7 @@ Spring XML:: - + ---- ==== @@ -235,31 +236,48 @@ Then we have routes where each of the routes uses their different policy: [tabs] ==== -Spring XML:: +Java:: ++ +[source,java] +---- +from("activemq:queue:inbox") + .transacted("txRequired") + .to("direct:audit") + .to("direct:order") + .to("activemq:queue:order"); + +from("direct:audit") + .transacted("txRequiresNew") + .bean("auditLogService", "insertAuditLog"); + +from("direct:order") + .transacted("txMandatory") + .bean("orderService", "insertOrder"); +---- + +XML:: + [source,xml] ---- - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + ---- YAML:: @@ -315,7 +333,7 @@ The sample is in its full as a https://github.com/apache/camel/blob/main/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/interceptor/TransactionalClientDataSourceWithOnExceptionMinimalConfigurationTest.java[unit test]. First, we set up the usual spring stuff in its configuration file. -Here we have defined a DataSource to the HSQLDB and most +Here we have defined a DataSource to the H2 database and most importantly the Spring `DataSourceTransactionManager` that is doing the heavy lifting of ensuring our transactional policies. @@ -359,45 +377,62 @@ as transacted using the `` XML tag. [tabs] ==== -Spring XML:: +Java:: ++ +[source,java] +---- +from("direct:okay") + // mark this route as transacted + .transacted() + .setBody(constant("Tiger in Action")) + .bean("bookService") + .setBody(constant("Elephant in Action")) + .bean("bookService"); + +from("direct:fail") + // mark this route as transacted + .transacted() + .setBody(constant("Tiger in Action")) + .bean("bookService") + .setBody(constant("Donkey in Action")) + .bean("bookService"); +---- + +XML:: + [source,xml] ---- - - - - - - - - Tiger in Action - - - - Elephant in Action - - - - - - - - - - Tiger in Action - - - - Donkey in Action - - - - - + + + + + + Tiger in Action + + + + Elephant in Action + + + + + + + + + + Tiger in Action + + + + Donkey in Action + + + ---- YAML:: @@ -449,7 +484,7 @@ Remember to use ``. The rest is standard Spring XML to set up the t In this sample, we want to listen for messages in a queue and process the messages with our business logic java code and send them along. Since it is based on a -https://github.com/apache/camel/tree/main/components/camel-jms/src/test/java/org/apache/camel/component/jms/tx/TransactionMinimalConfigurationTest.java[unit test], +https://github.com/apache/camel/blob/main/components/camel-jms/src/test/java/org/apache/camel/component/jms/integration/spring/tx/TransactionMinimalConfigurationIT.java[unit test], the destination is a mock endpoint. First, we configure the standard Spring XML to declare a JMS connection @@ -479,7 +514,7 @@ Spring XML:: - + @@ -497,22 +532,34 @@ route as transacted using the `` XML tag. [tabs] ==== -Spring XML:: +Java:: ++ +[source,java] +---- +// 1: from the jms queue +from("activemq:queue:okay") + // 2: mark this route as transacted + .transacted() + // 3: call our business logic that is myProcessor + .process("myProcessor") + // 4: if success then send it to the mock + .to("mock:result"); +---- + +XML:: + [source,xml] ---- - - - - - - - - - - - - + + + + + + + + + + ---- YAML:: diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc index a1ef60ca72415..c3556e98eaa25 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc @@ -24,7 +24,53 @@ into a Java object; or some other representation according to the data format be == Example -The following example reads XML files from the inbox/xml directory. +The following example reads JSON files from the inbox/json directory. `unmarshal` turns each file into a Map with +xref:dataformats:jackson2-dataformat.adoc[Jackson] (a JSON object becomes a Map, a JSON array a List), so the route can +read its fields, such as `${body[orderId]}`: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("file:inbox/json") + .unmarshal().json(JsonLibrary.Jackson) + .log("Order ${body[orderId]} for customer ${body[customer]}") + .to("bean:validateOrder"); +---- + +XML:: ++ +[source,xml] +---- + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: file:inbox/json + steps: + - unmarshal: + json: + library: Jackson + - log: + message: "Order ${body[orderId]} for customer ${body[customer]}" + - to: + uri: bean:validateOrder +---- +==== + +The next example reads XML files from the inbox/xml directory. Each file is then transformed into Java Objects using xref:dataformats:jaxb-dataformat.adoc[JAXB]. Then a xref:ROOT:bean-component.adoc[Bean] is invoked that takes in the Java object. diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jCharacterTokenizer.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jCharacterTokenizer.json index d968a5b47359b..3baf8ac1bc842 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jCharacterTokenizer.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jCharacterTokenizer.json @@ -17,6 +17,6 @@ "tokenizerType": { "index": 1, "kind": "attribute", "displayName": "Tokenizer Type", "group": "common", "required": true, "type": "enum", "javaType": "org.apache.camel.model.tokenizer.TokenizerType", "enum": [ "OPEN_AI", "AZURE", "QWEN" ], "deprecated": false, "autowired": false, "secret": false, "description": "The tokenizer type. Must be one of OPEN_AI, AZURE or QWEN." }, "maxTokens": { "index": 2, "kind": "attribute", "displayName": "Max Tokens", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens on each segment." }, "maxOverlap": { "index": 3, "kind": "attribute", "displayName": "Max Overlap", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens that can overlap in each segment." }, - "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } + "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jLineTokenizer.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jLineTokenizer.json index 843cf5419594b..364d2590da983 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jLineTokenizer.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jLineTokenizer.json @@ -17,6 +17,6 @@ "tokenizerType": { "index": 1, "kind": "attribute", "displayName": "Tokenizer Type", "group": "common", "required": true, "type": "enum", "javaType": "org.apache.camel.model.tokenizer.TokenizerType", "enum": [ "OPEN_AI", "AZURE", "QWEN" ], "deprecated": false, "autowired": false, "secret": false, "description": "The tokenizer type. Must be one of OPEN_AI, AZURE or QWEN." }, "maxTokens": { "index": 2, "kind": "attribute", "displayName": "Max Tokens", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens on each segment." }, "maxOverlap": { "index": 3, "kind": "attribute", "displayName": "Max Overlap", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens that can overlap in each segment." }, - "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } + "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jParagraphTokenizer.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jParagraphTokenizer.json index 4118c4afeb50c..c252e73498333 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jParagraphTokenizer.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jParagraphTokenizer.json @@ -17,6 +17,6 @@ "tokenizerType": { "index": 1, "kind": "attribute", "displayName": "Tokenizer Type", "group": "common", "required": true, "type": "enum", "javaType": "org.apache.camel.model.tokenizer.TokenizerType", "enum": [ "OPEN_AI", "AZURE", "QWEN" ], "deprecated": false, "autowired": false, "secret": false, "description": "The tokenizer type. Must be one of OPEN_AI, AZURE or QWEN." }, "maxTokens": { "index": 2, "kind": "attribute", "displayName": "Max Tokens", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens on each segment." }, "maxOverlap": { "index": 3, "kind": "attribute", "displayName": "Max Overlap", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens that can overlap in each segment." }, - "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } + "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jSentenceTokenizer.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jSentenceTokenizer.json index 1d8328d7fc916..869f54bc15785 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jSentenceTokenizer.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jSentenceTokenizer.json @@ -17,6 +17,6 @@ "tokenizerType": { "index": 1, "kind": "attribute", "displayName": "Tokenizer Type", "group": "common", "required": true, "type": "enum", "javaType": "org.apache.camel.model.tokenizer.TokenizerType", "enum": [ "OPEN_AI", "AZURE", "QWEN" ], "deprecated": false, "autowired": false, "secret": false, "description": "The tokenizer type. Must be one of OPEN_AI, AZURE or QWEN." }, "maxTokens": { "index": 2, "kind": "attribute", "displayName": "Max Tokens", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens on each segment." }, "maxOverlap": { "index": 3, "kind": "attribute", "displayName": "Max Overlap", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens that can overlap in each segment." }, - "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } + "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } } } diff --git a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jWordTokenizer.json b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jWordTokenizer.json index 57e842b9d8145..b5d89c3e823b2 100644 --- a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jWordTokenizer.json +++ b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/tokenizer/langChain4jWordTokenizer.json @@ -17,6 +17,6 @@ "tokenizerType": { "index": 1, "kind": "attribute", "displayName": "Tokenizer Type", "group": "common", "required": true, "type": "enum", "javaType": "org.apache.camel.model.tokenizer.TokenizerType", "enum": [ "OPEN_AI", "AZURE", "QWEN" ], "deprecated": false, "autowired": false, "secret": false, "description": "The tokenizer type. Must be one of OPEN_AI, AZURE or QWEN." }, "maxTokens": { "index": 2, "kind": "attribute", "displayName": "Max Tokens", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens on each segment." }, "maxOverlap": { "index": 3, "kind": "attribute", "displayName": "Max Overlap", "group": "common", "required": true, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "description": "The maximum number of tokens that can overlap in each segment." }, - "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } + "modelName": { "index": 4, "kind": "attribute", "displayName": "Model Name", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The underlying model name used by the tokenizer. Providing this switches to computing segment sizes in terms of tokens." } } } diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/tokenizer/LangChain4jTokenizerDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/tokenizer/LangChain4jTokenizerDefinition.java index 596bf1e8d6599..93410763536ed 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/tokenizer/LangChain4jTokenizerDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/tokenizer/LangChain4jTokenizerDefinition.java @@ -52,7 +52,7 @@ public class LangChain4jTokenizerDefinition extends TokenizerImplementationDefin description = "The maximum number of tokens that can overlap in each segment.") private String maxOverlap; - @XmlAttribute(required = true) + @XmlAttribute @Metadata(javaType = "java.lang.String", required = false, description = "The underlying model name used by the tokenizer." + " Providing this switches to computing segment sizes in terms of tokens.") diff --git a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json index 90d37e636eb8b..c8a033a3242ad 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json +++ b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json @@ -8,7 +8,15 @@ "aggregate": [ { "source": "aggregate-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - aggregate:\n aggregateController: myController\n aggregationStrategy: myAppender\n completionSize: 10\n correlationExpression:\n header:\n expression: id\n steps:\n - to:\n uri: mock:result\n" + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - aggregate:\n aggregationStrategy: groupedExchangeStrategy\n completionTimeout: 500\n correlationExpression:\n constant:\n expression: \"true\"\n steps:\n - to:\n uri: mock:result\n- beans:\n - name: groupedExchangeStrategy\n type: org.apache.camel.processor.aggregate.GroupedExchangeAggregationStrategy\n" + }, + { + "source": "aggregate-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:lines\n steps:\n - aggregate:\n correlationExpression:\n header:\n expression: orderId\n aggregationStrategy: groupedBodies\n completionSize: 10\n completionTimeout: 5000\n steps:\n - to:\n uri: direct:order\n- beans:\n - name: groupedBodies\n type: org.apache.camel.processor.aggregate.GroupedBodyAggregationStrategy\n" + }, + { + "source": "aggregate-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - aggregate:\n id: myAggregator\n aggregateController: myController\n aggregationStrategy: myAppender\n completionSize: 10\n correlationExpression:\n header:\n expression: id\n steps:\n - to:\n uri: mock:result\n" }, { "source": "aggregate-eip.adoc", @@ -18,6 +26,14 @@ "source": "aggregate-eip.adoc", "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - aggregate:\n aggregationStrategy: \"#class:com.foo.MyBodyAppender\"\n aggregationStrategyMethodName: append\n completionSize: 3\n correlationExpression:\n constant:\n expression: \"true\"\n steps:\n - to:\n uri: mock:result\n" }, + { + "source": "aggregate-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - aggregate:\n aggregationStrategy: \"#class:com.foo.MyBodyAppender\"\n completionSize: 3\n correlationExpression:\n constant:\n expression: \"true\"\n steps:\n - to:\n uri: mock:result\n" + }, + { + "source": "aggregate-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - pollEnrich:\n expression:\n constant:\n expression: seda:foo\n timeout: 1000\n aggregationStrategy: myAppender\n aggregationStrategyMethodName: append\n aggregationStrategyMethodAllowNull: true\n - to:\n uri: mock:result\n" + }, { "source": "aggregate-eip.adoc", "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - aggregate:\n aggregationStrategy: myAppender\n aggregationStrategyMethodName: append\n aggregationStrategyMethodAllowNull: true\n completionSize: 3\n correlationExpression:\n constant:\n expression: \"true\"\n steps:\n - to:\n uri: mock:result\n" @@ -90,7 +106,23 @@ "cache": [ { "source": "cache-eip.adoc", - "yaml": "- from:\n uri: \"direct:start\"\n steps:\n - cache:\n simple: \"${header.productId}\"\n keyValueRepository: \"myCache\"\n ttl: \"10m\"\n steps:\n - to: \"http:\/\/product-service\/api\/product\"\n - to: \"direct:continue\"\n" + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - cache:\n expression:\n simple:\n expression: \"${header.productId}\"\n steps:\n - to:\n uri: http:\/\/product-service\/api\/product\n - unmarshal:\n json: {}\n - to:\n uri: direct:continue\n" + }, + { + "source": "cache-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - cache:\n expression:\n simple:\n expression: \"${header.productId}\"\n keyValueRepository: myCache\n ttl: 10m\n steps:\n - to:\n uri: http:\/\/product-service\/api\/product\n - to:\n uri: direct:continue\n" + }, + { + "source": "cache-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - cache:\n expression:\n simple:\n expression: \"${header.key}\"\n keyValueRepository: myCache\n steps:\n - to:\n uri: http:\/\/service\n" + }, + { + "source": "cache-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:getProduct\n steps:\n - cache:\n expression:\n simple:\n expression: \"${header.productId}\"\n keyValueRepository: productCache\n ttl: 30m\n steps:\n - to:\n uri: http:\/\/product-service\/api\/product\n\n# Invalidation route\n- route:\n from:\n uri: jms:topic:product.changes\n steps:\n - bean:\n ref: productCache\n method: \"delete(${header.productId})\"\n - log:\n message: \"Invalidated cache for product ${header.productId}\"\n" + }, + { + "source": "cache-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:clearAll\n steps:\n - bean:\n ref: productCache\n method: clear\n" } ], "channelAdapter": [ @@ -112,18 +144,14 @@ "circuitBreaker": [ { "source": "circuitBreaker-eip.adoc", - "yaml": "- route:\n from:\n uri: timer:stock?period=1000\n steps:\n - circuitBreaker:\n steps:\n - to:\n uri: direct:supplier\n onFallback:\n steps:\n - choice:\n when:\n - expression:\n simple:\n expression: \"${exchangeProperty.CamelCircuitBreakerResponseRejected} == true\"\n steps:\n - setBody:\n expression:\n constant:\n expression: \"supplier is down, using the last known stock\"\n otherwise:\n steps:\n - setBody:\n expression:\n constant:\n expression: \"stock check failed, will try again\"\n" + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - circuitBreaker:\n steps:\n - to:\n uri: http:\/\/fooservice.com\/slow\n onFallback:\n steps:\n - transform:\n expression:\n constant:\n expression: Fallback message\n - to:\n uri: mock:result\n" }, { "source": "circuitBreaker-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - circuitBreaker:\n steps:\n - to:\n uri: http:\/\/fooservice.com\/slow\n onFallback:\n steps:\n - transform:\n expression:\n constant:\n expression: Fallback message\n - to:\n uri: mock:result\n" + "yaml": "- route:\n from:\n uri: timer:stock?period=1000\n steps:\n - circuitBreaker:\n steps:\n - to:\n uri: direct:supplier\n onFallback:\n steps:\n - choice:\n when:\n - expression:\n simple:\n expression: \"${exchangeProperty.CamelCircuitBreakerResponseRejected} == true\"\n steps:\n - setBody:\n expression:\n constant:\n expression: \"supplier is down, using the last known stock\"\n otherwise:\n steps:\n - setBody:\n expression:\n constant:\n expression: \"stock check failed, will try again\"\n" } ], "claimCheck": [ - { - "source": "claimCheck-eip.adoc", - "yaml": "- route:\n from:\n uri: direct\n parameters:\n name: start\n steps:\n - to:\n uri: mock\n parameters:\n name: a\n - claimCheck:\n operation: Set\n key: \"${header.myKey}\"\n - transform:\n expression:\n constant:\n expression: Bye World\n - to:\n uri: mock\n parameters:\n name: b\n - claimCheck:\n operation: Get\n key: \"${header.myKey}\"\n - to:\n uri: mock\n parameters:\n name: c\n - transform:\n expression:\n constant:\n expression: Hi World\n - to:\n uri: mock\n parameters:\n name: d\n - claimCheck:\n operation: Get\n key: \"${header.myKey}\"\n - to:\n uri: mock\n parameters:\n name: e\n" - }, { "source": "claimCheck-eip.adoc", "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - to: mock:a\n - claimCheck:\n operation: Push\n - transform:\n expression:\n constant: Bye World\n - to:\n uri: mock:b\n - claimCheck:\n operation: Pop\n - to:\n uri: mock:c\n" @@ -135,6 +163,10 @@ { "source": "claimCheck-eip.adoc", "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - to:\n uri: mock:a\n - claimCheck:\n operation: Push\n - transform:\n expression:\n constant: ByeWorld\n - setHeader:\n name: foo\n expression:\n constant: 456\n - removeHeader:\n name: bar\n - to:\n uri: mock:b\n - claimCheck:\n operation: Pop\n filter: \"header:(foo|bar)\"\n - to:\n uri: mock:c\n" + }, + { + "source": "claimCheck-eip.adoc", + "yaml": "- route:\n from:\n uri: direct\n parameters:\n name: start\n steps:\n - to:\n uri: mock\n parameters:\n name: a\n - claimCheck:\n operation: Set\n key: \"${header.myKey}\"\n - transform:\n expression:\n constant:\n expression: Bye World\n - to:\n uri: mock\n parameters:\n name: b\n - claimCheck:\n operation: Get\n key: \"${header.myKey}\"\n - to:\n uri: mock\n parameters:\n name: c\n - transform:\n expression:\n constant:\n expression: Hi World\n - to:\n uri: mock\n parameters:\n name: d\n - claimCheck:\n operation: Get\n key: \"${header.myKey}\"\n - to:\n uri: mock\n parameters:\n name: e\n" } ], "competingConsumers": [ @@ -164,29 +196,29 @@ }, { "source": "content-enricher.adoc", - "yaml": "- route:\n from:\n uri: direct\n parameters:\n name: start\n steps:\n - setBody:\n expression:\n simple:\n expression: \"${body} World!\"\n - to:\n uri: mock\n parameters:\n name: result\n" + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - setBody:\n expression:\n simple:\n expression: \"${body} World!\"\n - to:\n uri: mock:result\n" }, { "source": "content-enricher.adoc", - "yaml": "- route:\n from:\n uri: direct\n parameters:\n name: start\n steps:\n - process:\n ref: myProcessor\n - to:\n uri: mock\n parameters:\n name: result\n" + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - process:\n ref: myProcessor\n - to:\n uri: mock:result\n" }, { "source": "content-enricher.adoc", - "yaml": "- route:\n from:\n uri: activemq:Input\n steps:\n - bean:\n ref: myBeanName\n method: doTransform\n - to:\n uri: activemq:Output\n" + "yaml": "- route:\n from:\n uri: activemq:My.Queue\n steps:\n - bean:\n ref: myBeanName\n method: doTransform\n - to:\n uri: activemq:Another.Queue\n" } ], "contentFilter": [ { "source": "content-filter-eip.adoc", - "yaml": "- route:\n from:\n uri: direct\n parameters:\n name: start\n steps:\n - process:\n ref: myProcessor\n - to:\n uri: mock\n parameters:\n name: result\n" + "yaml": "- route:\n from:\n uri: activemq:Input\n steps:\n - setBody:\n expression:\n xpath:\n expression: \/\/foo\/bar\n - to:\n uri: activemq:Output\n" }, { "source": "content-filter-eip.adoc", - "yaml": "- route:\n from:\n uri: activemq:Input\n steps:\n - bean:\n ref: myBeanName\n method: doFilter\n - to:\n uri: activemq:Output\n" + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - process:\n ref: myProcessor\n - to:\n uri: mock:result\n" }, { "source": "content-filter-eip.adoc", - "yaml": "- route:\n from:\n uri: activemq:Input\n steps:\n - setBody:\n expression:\n xpath: \/\/foo:bar\n - to:\n uri: activemq:Output\n" + "yaml": "- route:\n from:\n uri: activemq:Input\n steps:\n - bean:\n ref: myBeanName\n method: doFilter\n - to:\n uri: activemq:Output\n" } ], "convertBodyTo": [ @@ -246,6 +278,10 @@ "source": "dead-letter-channel.adoc", "yaml": "- errorHandler:\n deadLetterChannel:\n deadLetterUri: \"log:dead?level=ERROR\"\n" }, + { + "source": "dead-letter-channel.adoc", + "yaml": "- errorHandler:\n deadLetterChannel:\n deadLetterUri: \"log:dead?level=ERROR\"\n- route:\n from:\n uri: direct:newOrder\n steps:\n - bean:\n ref: orderService\n method: validate\n - bean:\n ref: orderService\n method: store\n" + }, { "source": "dead-letter-channel.adoc", "yaml": "- errorHandler:\n deadLetterChannel:\n deadLetterUri: jms:queue:dead\n redeliveryPolicy:\n maximumRedeliveries: 3\n redeliveryDelay: \"5000\"\n" @@ -441,6 +477,16 @@ "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - to:\n uri: jms:queue:foo\n" } ], + "idempotentConsumer": [ + { + "source": "idempotentConsumer-eip.adoc", + "yaml": "- beans:\n - name: myRepo\n type: org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository\n- route:\n from:\n uri: direct:start\n steps:\n - idempotentConsumer:\n expression:\n header:\n expression: messageId\n idempotentRepository: myRepo\n steps:\n - to:\n uri: mock:result\n" + }, + { + "source": "idempotentConsumer-eip.adoc", + "yaml": "- beans:\n - name: myRepo\n type: org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository\n- route:\n from:\n uri: direct:start\n steps:\n - idempotentConsumer:\n expression:\n header:\n expression: messageId\n idempotentRepository: myRepo\n skipDuplicate: false\n steps:\n - choice:\n when:\n - expression:\n simple:\n expression: \"${exchangeProperty.CamelDuplicateMessage} == true\"\n steps:\n - log:\n message: \"Message ${header.messageId} is a duplicate\"\n otherwise:\n steps:\n - to:\n uri: mock:result\n" + } + ], "intercept": [ { "source": "intercept.adoc", @@ -500,10 +546,6 @@ } ], "kamelet": [ - { - "source": "kamelet-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - to:\n uri: kamelet:my-aggregate\n parameters:\n count: 5\n - to:\n uri: log:info\n - to:\n uri: mock:result\n" - }, { "source": "kamelet-eip.adoc", "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - kamelet:\n name: my-aggregate?count=5\n steps:\n - to:\n uri: log:info\n - to:\n uri: mock:result\n" @@ -590,7 +632,7 @@ }, { "source": "log-eip.adoc", - "yaml": "- route:\n id: moo\n from:\n uri: direct:moo\n steps:\n - log:\n logger: myLogger\n message: \"Me Got ${body}\"\n - to:\n uri: mock:baz\n" + "yaml": "- route:\n from:\n uri: direct:moo\n steps:\n - log:\n logger: myLogger\n message: \"Me Got ${body}\"\n - to:\n uri: bean:foo\n- beans:\n - name: myLogger\n type: org.slf4j.LoggerFactory\n factoryMethod: getLogger\n constructors:\n 0: com.mycompany.mylogger\n" }, { "source": "log-eip.adoc", @@ -624,6 +666,10 @@ } ], "marshal": [ + { + "source": "marshal-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:order\n steps:\n - marshal:\n json:\n library: Jackson\n - to:\n uri: jms:queue:order\n" + }, { "source": "marshal-eip.adoc", "yaml": "- route:\n from:\n uri: file:inbox\/xml\n steps:\n - unmarshal:\n jaxb:\n contextPath: com.acme.order\n - to:\n uri: bean:validateOrder\n - marshal:\n jaxb:\n contextPath: com.acme.order\n - to:\n uri: jms:queue:order\n" @@ -769,6 +815,16 @@ "yaml": "- onException:\n exception:\n - org.apache.camel.ValidationException\n steps:\n - to:\n uri: activemq:validationFailed\n- onException:\n exception:\n - com.foo.ShipOrderException\n steps:\n - to:\n uri: activemq:shipFailed\n- route:\n from:\n uri: seda:order\n steps:\n - to:\n uri: bean:processOrder\n" } ], + "pausable": [ + { + "source": "pausable-eip.adoc", + "yaml": "- route:\n from:\n uri: kafka:topic\n steps:\n - pausable:\n consumerListener: kafkaConsumerListener\n untilCheck: canContinue\n - log:\n message: \"Received: ${body}\"\n - to:\n uri: direct:destination\n- beans:\n - name: kafkaConsumerListener\n type: org.apache.camel.component.kafka.consumer.errorhandler.KafkaConsumerListener\n - name: canContinue\n type: com.foo.CanContinuePredicate\n" + }, + { + "source": "pausable-eip.adoc", + "yaml": "- route:\n from:\n uri: kafka:topic\n steps:\n - pausable:\n consumerListener: kafkaConsumerListener\n untilCheck: canContinue\n - circuitBreaker:\n resilience4jConfiguration:\n circuitBreaker: pausableCircuit\n steps:\n - to:\n uri: direct:downstream\n" + } + ], "pipeline": [ { "source": "pipeline-eip.adoc", @@ -806,18 +862,26 @@ "pollEnrich": [ { "source": "pollEnrich-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - pollEnrich:\n aggregationStrategy: myStrategy\n timeout: 10000\n expression:\n constant:\n expression: file:inbox?fileName=data.txt\n - to:\n uri: mock:result\n" + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - pollEnrich:\n timeout: 10000\n expression:\n constant:\n expression: file:inbox?fileName=data.txt\n - to:\n uri: mock:result\n" }, { "source": "pollEnrich-eip.adoc", - "yaml": "- rest:\n path: \/report\n get:\n - path: \/{id}\/report\n to:\n uri: direct:report\n\n- route:\n from:\n uri: direct:report\n steps:\n - pollEnrich:\n expression:\n constant:\n expression: \"aws-s3:xavier-dev?amazonS3Client=#s3client&deleteAfterRead=false&fileName=report-file.pdf\"\n" + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - pollEnrich:\n expression:\n simple:\n expression: \"seda:${header.queueName}\"\n - to:\n uri: direct:result\n" }, { "source": "pollEnrich-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - pollEnrich:\n expression:\n simple:\n expression: \"seda:${header.queueName}\"\n - to:\n uri: direct:result\n" + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - pollEnrich:\n aggregationStrategy: myStrategy\n timeout: 10000\n expression:\n constant:\n expression: file:inbox?fileName=data.txt\n - to:\n uri: mock:result\n" + }, + { + "source": "pollEnrich-eip.adoc", + "yaml": "- rest:\n path: \/report\n get:\n - path: \/{id}\/report\n to:\n uri: direct:report\n\n- route:\n from:\n uri: direct:report\n steps:\n - pollEnrich:\n expression:\n constant:\n expression: \"aws-s3:xavier-dev?amazonS3Client=#s3client&deleteAfterRead=false&fileName=report-file.pdf\"\n" } ], "process": [ + { + "source": "process-eip.adoc", + "yaml": "- route:\n from:\n uri: activemq:myQueue\n steps:\n - process:\n ref: myProcessor\n- beans:\n - name: myProcessor\n type: com.foo.MyProcessor\n" + }, { "source": "process-eip.adoc", "yaml": "- route:\n from:\n uri: activemq:myQueue\n steps:\n - process:\n ref: \"#class:com.foo.MyProcessor\"\n" @@ -860,6 +924,10 @@ "source": "recipientList-eip.adoc", "yaml": "- route:\n from:\n uri: direct:a\n steps:\n - recipientList:\n ignoreInvalidEndpoints: \"true\"\n expression:\n header:\n expression: myHeader\n" }, + { + "source": "recipientList-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - multicast:\n aggregationStrategy: myStrategy\n parallelProcessing: true\n timeout: 250\n steps:\n - to:\n uri: direct:a\n - to:\n uri: direct:b\n - to:\n uri: direct:c\n - to:\n uri: mock:result\n- route:\n from:\n uri: direct:a\n steps:\n - delay:\n expression:\n constant:\n expression: \"1000\"\n - to:\n uri: mock:A\n - setBody:\n expression:\n constant:\n expression: A\n- route:\n from:\n uri: direct:b\n steps:\n - to:\n uri: mock:B\n - setBody:\n expression:\n constant:\n expression: B\n- route:\n from:\n uri: direct:c\n steps:\n - to:\n uri: mock:C\n - setBody:\n expression:\n constant:\n expression: C\n- beans:\n - name: myStrategy\n type: com.foo.MyAggregationStrategy\n" + }, { "source": "recipientList-eip.adoc", "yaml": "- route:\n from:\n uri: file:inbox\n steps:\n - recipientList:\n expression:\n constant:\n expression: activemq:queue:inbox?exchangePattern=InOut\n - to:\n uri: file:outbox\n" @@ -1049,6 +1117,46 @@ "yaml": "- restConfiguration:\n inlineRoutes: true\n- rest:\n path: \"\/customers\/\"\n get:\n - path: \"\/{id}\"\n to: \"direct:customerDetails\"\n - path: \"\/{id}\/orders\"\n to: \"direct:customerOrders\"\n post:\n - path: \"\/neworder\"\n to: \"direct:customerNewOrder\"\n- route:\n from:\n uri: direct:customerNewOrder\n steps:\n - to:\n uri: direct:commonCustomerNewOrder\n- route:\n from:\n uri: direct:commonCustomerNewOrder\n steps:\n - log:\n message: Created new order\n- route:\n from:\n uri: kafka:new-order\n steps:\n - to:\n uri: direct:commonCustomerNewOrder\n" } ], + "resumable": [ + { + "source": "resumable-eip.adoc", + "yaml": "- route:\n from:\n uri: some:component\n steps:\n - resumable:\n resumeStrategy: kafkaResumeStrategy\n - process:\n ref: myProcessor\n" + }, + { + "source": "resumable-eip.adoc", + "yaml": "- route:\n from:\n uri: some:component\n steps:\n - resumable:\n resumeStrategy: testResumeStrategy\n intermittent: true\n - process:\n ref: myProcessor\n- beans:\n - name: testResumeStrategy\n type: com.foo.MyTestResumeStrategy\n" + } + ], + "resumeStrategies": [ + { + "source": "resume-strategies.adoc", + "yaml": "- beans:\n - name: testResumeStrategy\n type: com.foo.MyTestResumeStrategy\n - name: resumeCache\n type: com.foo.MyChoiceOfResumeCache\n constructors:\n 0: 100\n- route:\n from:\n uri: some:component\n steps:\n - resumable:\n resumeStrategy: testResumeStrategy\n - process:\n ref: myProcessor\n" + }, + { + "source": "resume-strategies.adoc", + "yaml": "- beans:\n - name: resumeCache\n type: com.foo.MyChoiceOfResumeCache\n constructors:\n 0: 100\n- route:\n from:\n uri: some:component\n steps:\n - resumable:\n resumeStrategy: \"#class:com.foo.MyTestResumeStrategy\"\n - process:\n ref: myProcessor\n" + }, + { + "source": "resume-strategies.adoc", + "yaml": "- route:\n from:\n uri: some:component\n steps:\n - resumable:\n resumeStrategy: testResumeStrategy\n intermittent: true\n - process:\n ref: myProcessor\n- beans:\n - name: testResumeStrategy\n type: com.foo.MyTestResumeStrategy\n" + }, + { + "source": "resume-strategies.adoc", + "yaml": "- route:\n from:\n uri: \"file:{{input.dir}}\"\n parameters:\n noop: true\n fileName: \"{{input.file}}\"\n steps:\n - resumable:\n resumeStrategy: testResumeStrategy\n - convertBodyTo:\n type: java.io.Reader\n - process:\n ref: myProcessor\n" + }, + { + "source": "resume-strategies.adoc", + "yaml": "- route:\n from:\n uri: \"file:{{input.dir}}\"\n parameters:\n noop: true\n fileName: \"{{input.file}}\"\n steps:\n - resumable:\n resumeStrategy: testResumeStrategy\n - convertBodyTo:\n type: java.io.InputStream\n - process:\n ref: myProcessor\n" + }, + { + "source": "resume-strategies.adoc", + "yaml": "- route:\n from:\n uri: kafka:topic\n steps:\n - pausable:\n consumerListener: kafkaConsumerListener\n untilCheck: canContinue\n - log:\n message: \"Received an exchange: ${body}\"\n - to:\n uri: direct:destination\n- beans:\n - name: kafkaConsumerListener\n type: org.apache.camel.component.kafka.consumer.errorhandler.KafkaConsumerListener\n - name: canContinue\n type: com.foo.CanContinuePredicate\n" + }, + { + "source": "resume-strategies.adoc", + "yaml": "- route:\n id: pausable-it\n from:\n uri: kafka:topic\n steps:\n - pausable:\n consumerListener: kafkaConsumerListener\n untilCheck: canContinue\n - log:\n message: \"Got record from Kafka: ${body}\"\n - circuitBreaker:\n resilience4jConfiguration:\n circuitBreaker: pausableCircuit\n steps:\n - to:\n uri: direct:downstream\n" + } + ], "returnAddress": [ { "source": "return-address.adoc", @@ -1356,15 +1464,15 @@ }, { "source": "split-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:a\n steps:\n - split:\n delimiter: \"\\n\"\n expression:\n simple:\n expression: \"${body}\"\n steps:\n - to:\n uri: direct:b\n" + "yaml": "- route:\n from:\n uri: direct:splitUsingBody\n steps:\n - split:\n expression:\n simple:\n expression: \"${body}\"\n steps:\n - to:\n uri: mock:result\n" }, { "source": "split-eip.adoc", - "yaml": "- route:\n from:\n uri: activemq:my.queue\n steps:\n - split:\n expression:\n xpath:\n expression: \/\/foo\/bar\n steps:\n - to:\n uri: file:some\/directory\n" + "yaml": "- route:\n from:\n uri: direct:a\n steps:\n - split:\n delimiter: \"\\n\"\n expression:\n simple:\n expression: \"${body}\"\n steps:\n - to:\n uri: direct:b\n" }, { "source": "split-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:splitUsingBody\n steps:\n - split:\n expression:\n simple:\n expression: \"${body}\"\n steps:\n - to:\n uri: mock:result\n" + "yaml": "- route:\n from:\n uri: activemq:my.queue\n steps:\n - split:\n expression:\n xpath:\n expression: \/\/foo\/bar\n steps:\n - to:\n uri: file:some\/directory\n" }, { "source": "split-eip.adoc", @@ -1432,7 +1540,7 @@ }, { "source": "split-eip.adoc", - "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - to:\n uri: mock:a\n - split:\n shareUnitOfWork: \"true\"\n expression:\n tokenize:\n token: \",\"\n steps:\n - to:\n uri: mock:b\n - to:\n uri: direct:line\n - to:\n uri: mock:result\n\n- route:\n from:\n uri: direct:line\n steps:\n - to:\n uri: log:line\n - process:\n ref: myProcessor\n - to:\n uri: mock:line\n" + "yaml": "- errorHandler:\n deadLetterChannel:\n deadLetterUri: mock:dead\n useOriginalMessage: true\n redeliveryPolicy:\n maximumRedeliveries: 3\n redeliveryDelay: \"0\"\n- route:\n from:\n uri: direct:start\n steps:\n - to:\n uri: mock:a\n - split:\n shareUnitOfWork: \"true\"\n expression:\n tokenize:\n token: \",\"\n steps:\n - to:\n uri: mock:b\n - to:\n uri: direct:line\n - to:\n uri: mock:result\n\n- route:\n from:\n uri: direct:line\n steps:\n - to:\n uri: log:line\n - process:\n ref: myProcessor\n - to:\n uri: mock:line\n" } ], "sslContextParameters": [ @@ -1586,7 +1694,17 @@ }, { "source": "toD-eip.adoc", - "yaml": "- from:\n uri: direct:start\n steps:\n - toD:\n uri: \"${header.target}\"\n allowedSchemes: \"http,https\"\n" + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - toD:\n uri: \"${header.target}\"\n allowedSchemes: \"http,https\"\n" + } + ], + "tokenizer": [ + { + "source": "tokenizer-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - tokenizer:\n langChain4jParagraphTokenizer:\n maxTokens: 1024\n maxOverlap: 10\n tokenizerType: OPEN_AI\n - split:\n expression:\n simple:\n expression: \"${body}\"\n steps:\n - to:\n uri: mock:result\n" + }, + { + "source": "tokenizer-eip.adoc", + "yaml": "- route:\n from:\n uri: direct:start\n steps:\n - tokenizer:\n langChain4jParagraphTokenizer:\n maxTokens: 1024\n modelName: gpt-4o-mini\n maxOverlap: 10\n tokenizerType: OPEN_AI\n - split:\n expression:\n simple:\n expression: \"${body}\"\n steps:\n - to:\n uri: mock:result\n" } ], "topicLoadBalancer": [ @@ -1652,6 +1770,10 @@ } ], "unmarshal": [ + { + "source": "unmarshal-eip.adoc", + "yaml": "- route:\n from:\n uri: file:inbox\/json\n steps:\n - unmarshal:\n json:\n library: Jackson\n - log:\n message: \"Order ${body[orderId]} for customer ${body[customer]}\"\n - to:\n uri: bean:validateOrder\n" + }, { "source": "unmarshal-eip.adoc", "yaml": "- route:\n from:\n uri: file:inbox\/xml\n steps:\n - unmarshal:\n jaxb:\n contextPath: com.acme.order\n - to:\n uri: bean:validateOrder\n - marshal:\n jaxb:\n contextPath: com.acme.order\n - to:\n uri: jms:queue:order\n" diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java index 4a6390156edae..0bb0209f06e3f 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java @@ -249,7 +249,36 @@ static Found fromCatalog(CamelCatalog catalog, String kind, String key) { static final Map SAMPLES_ON_PAGE_OF = Map.of("interceptFrom", "intercept", "interceptSendToEndpoint", "intercept"); + /** + * The EIPs whose own page has no example: their samples are the examples on the pages of what they belong to that + * write them, the first of each page first (CAMEL-25774). loadBalance is on the pages of its policies, the circuit + * breaker settings on the pages of Resilience4j and Fault Tolerance, and the resequencer settings on its page. + */ + static final Map> SAMPLES_IN_PAGES_OF = Map.of( + "loadBalance", List.of("failoverLoadBalancer-eip", "roundRobinLoadBalancer-eip", "weightedLoadBalancer-eip", + "randomLoadBalancer-eip", "stickyLoadBalancer-eip", "topicLoadBalancer-eip", "customLoadBalancer-eip"), + "resilience4jConfiguration", List.of("resilience4j-eip"), + "faultToleranceConfiguration", List.of("fault-tolerance-eip"), + "batchConfig", List.of("resequence-eip"), + "streamConfig", List.of("resequence-eip")); + + /** The settings written inside an EIP as one of its options, not as a step (CAMEL-25774). */ + static final Map SETTINGS_OF = Map.of( + "resilience4jConfiguration", "circuitBreaker", "faultToleranceConfiguration", "circuitBreaker", + "batchConfig", "resequence", "streamConfig", "resequence"); + private static Found eipSamples(CamelCatalog catalog, String key) { + List pages = SAMPLES_IN_PAGES_OF.get(key); + if (pages != null) { + Pattern writes = Pattern.compile("(?m)^\\s*(- )?" + Pattern.quote(key) + ":"); + List>> perPage = new ArrayList<>(); + for (String page : pages) { + perPage.add(pageSamples(catalog, List.of(page), null).samples().stream() + .filter(s -> writes.matcher(s.get("yaml")).find()).toList()); + } + List> all = interleave(perPage); + return all.isEmpty() ? Found.NONE : new Found(all, false); + } String host = SAMPLES_ON_PAGE_OF.get(key); if (host != null) { // the examples of the page that start with the entry, not those of the EIP the page is about @@ -266,6 +295,20 @@ private static Found eipSamples(CamelCatalog catalog, String key) { return Found.NONE; } + /** The samples of each page in turn: the first of every page, then the second, and so on. */ + private static List> interleave(List>> perPage) { + List> answer = new ArrayList<>(); + int longest = perPage.stream().mapToInt(List::size).max().orElse(0); + for (int i = 0; i < longest; i++) { + for (List> page : perPage) { + if (i < page.size()) { + answer.add(page.get(i)); + } + } + } + return answer; + } + private static Found componentSamples(CamelCatalog catalog, String name) { String page = componentPage(catalog, name); if (page == null) { @@ -774,6 +817,10 @@ static String placement(String key) { if (TOP_LEVEL.contains(key)) { return "top-level entry: a list item at the same level as route or from, not a step inside a route"; } + String owner = SETTINGS_OF.get(key); + if (owner != null) { + return "the settings of " + owner + ": written inside " + owner + " as " + key + ":, not as a step"; + } return "a step inside the steps of a route (or of another EIP)"; } @@ -858,6 +905,12 @@ static String resolveExact(String given) { return k; } } + // an EIP whose examples are on the pages of what it belongs to (CAMEL-25774) + for (String k : SAMPLES_IN_PAGES_OF.keySet()) { + if (k.equalsIgnoreCase(n)) { + return k; + } + } return null; } diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java index e626e7100eb5a..b93f3d60f0a30 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java @@ -680,9 +680,12 @@ void placementByKindOfEip() { assertFalse(((JsonObject) o).containsKey("yaml"), ((JsonObject) o).toJson()); } assertTrue(doc.getString("sampleHint").contains("camel_catalog_sample name=split"), doc.toJson()); - // the sample hint only for an EIP camel_catalog_sample has a sample of + // the sample hint only for an EIP camel_catalog_sample has a sample of: pausable has one since its page has YAML + // examples (CAMEL-25774); a tokenizer implementation, written inside tokenizer, has none doc = CatalogDocs.catalogDoc(catalog, "pausable", null, "eip", null, null, false, false, null); assertTrue(doc.getString("placement").startsWith("a step inside the steps of a route"), doc.toJson()); + assertTrue(doc.getString("sampleHint").contains("camel_catalog_sample name=pausable"), doc.toJson()); + doc = CatalogDocs.catalogDoc(catalog, "langChain4jParagraphTokenizer", null, "eip", null, null, false, false, null); assertFalse(doc.containsKey("sampleHint"), doc.toJson()); } diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java index 875ab98eac0b8..16ca551d75ee9 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java @@ -56,6 +56,41 @@ void theInterceptEntriesAndDataFormatsHaveSamples() { } } + @Test + void theEipsWithoutExamplesOfTheirOwnHaveSamplesFromThePagesTheyBelongTo() { + // CAMEL-25774: loadBalance is on the pages of its policies, the circuit breaker settings on the Resilience4j and + // Fault Tolerance pages, and the resequencer settings on its page; a sample writes the key it is asked for + CamelCatalog catalog = new DefaultCamelCatalog(); + for (String entry : List.of("loadBalance", "resilience4jConfiguration", "faultToleranceConfiguration", + "batchConfig", "streamConfig")) { + JsonArray samples = (JsonArray) CatalogSamples.sample(catalog, entry, 5).get("samples"); + assertThat(samples).as(entry).isNotEmpty(); + for (Object s : samples) { + assertThat(((JsonObject) s).getString("yaml")).as(entry).contains(entry + ":"); + } + } + // the first sample of each policy page first, so the first two show two policies + JsonArray lb = (JsonArray) CatalogSamples.sample(catalog, "loadBalance", 2).get("samples"); + assertThat(((JsonObject) lb.get(0)).getString("source")).startsWith("failoverLoadBalancer-eip"); + assertThat(((JsonObject) lb.get(1)).getString("source")).startsWith("roundRobinLoadBalancer-eip"); + // the settings are written inside the EIP, not as a step + assertThat(CatalogSamples.sample(catalog, "streamConfig", 1).getString("placement")) + .contains("written inside resequence").contains("not as a step"); + } + + @Test + void theIdempotentConsumerHasSamples() { + // CAMEL-25774: its page had no example, so the sample tool answered "no YAML sample" + JsonArray samples = (JsonArray) CatalogSamples.sample(new DefaultCamelCatalog(), "idempotentConsumer", 2) + .get("samples"); + assertThat(samples).hasSize(2); + assertThat(((JsonObject) samples.get(0)).getString("yaml")).contains("idempotentRepository: myRepo") + .contains("MemoryIdempotentRepository"); + // each sample declares the repository it uses, so it works on its own + assertThat(((JsonObject) samples.get(1)).getString("yaml")).contains("skipDuplicate: false") + .contains("CamelDuplicateMessage").contains("MemoryIdempotentRepository"); + } + @Test void theTopLevelEntriesDocumentedOnAnotherPageHaveSamples() { // CAMEL-25719: their examples are on the page of what they belong to (route templates, the REST DSL, @@ -103,11 +138,11 @@ void eipAliasesResolveThroughTheCatalog() { // an exact component name (chunk is the Chunk templating component) beats an alias of an EIP, unless asked assertThat(CatalogSamples.sample(catalog, "chunk", 1).getString("kind")).isEqualTo("component"); assertThat(CatalogSamples.sample(catalog, "eip", "chunk", 1).getString("name")).isEqualTo("split"); - // an alias of an EIP whose page has no YAML example still names the EIP + // dedup is the idempotent consumer, which has samples since its page has examples (CAMEL-25774) JsonObject dedup = CatalogSamples.sample(catalog, "dedup", 1); - assertThat(dedup.getString("error")).contains("dedup"); - assertThat(dedup.getString("eip")).isEqualTo("idempotentConsumer"); - assertThat(dedup.getString("hint")).contains("idempotentConsumer EIP"); + assertThat(dedup.getString("name")).isEqualTo("idempotentConsumer"); + assertThat(dedup.getString("note")).contains("idempotentConsumer EIP"); + assertThat((JsonArray) dedup.get("samples")).isNotEmpty(); assertThat(CatalogSamples.sample(catalog, "rate-limit", 1).getString("name")).isEqualTo("throttle"); assertThat(CatalogSamples.sample(catalog, "router", 1).getString("name")).isEqualTo("choice"); // an aliased part of an EIP shows the whole @@ -149,6 +184,7 @@ void callingAServiceIsTheHttpComponentNotEnrich() { JsonObject o = CatalogSamples.sample(catalog, term, 2); assertThat(o.getString("name")).as(term).isEqualTo("http"); assertThat(o.getString("note")).as(term).contains("to:", "toD:"); + // the uri may be quoted, as in the toD of the GET example on the http page (CAMEL-25745) assertThat(((JsonObject) ((JsonArray) o.get("samples")).get(0)).getString("yaml")) .containsPattern("uri: \"?http"); } diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json index bab314bd2401f..31cc9a2ad77f1 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl-model.json @@ -19651,8 +19651,7 @@ "displayName" : "Model Name", "kind" : "attribute", "index" : 4, - "group" : "common", - "required" : true + "group" : "common" } ] }, "langChain4jLineTokenizer" : { @@ -19707,8 +19706,7 @@ "displayName" : "Model Name", "kind" : "attribute", "index" : 4, - "group" : "common", - "required" : true + "group" : "common" } ] }, "langChain4jParagraphTokenizer" : { @@ -19763,8 +19761,7 @@ "displayName" : "Model Name", "kind" : "attribute", "index" : 4, - "group" : "common", - "required" : true + "group" : "common" } ] }, "langChain4jSentenceTokenizer" : { @@ -19819,8 +19816,7 @@ "displayName" : "Model Name", "kind" : "attribute", "index" : 4, - "group" : "common", - "required" : true + "group" : "common" } ] }, "langChain4jTokenizer" : { @@ -19897,8 +19893,7 @@ "displayName" : "Model Name", "kind" : "attribute", "index" : 4, - "group" : "common", - "required" : true + "group" : "common" } ] }, "customTransformer" : {