This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 1410dea38615fbeb6bc1b88be582cba7c6d370bb Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Mon Oct 11 14:43:42 2021 +0200 CAMEL-16861: Cleanup and update EIP docs --- .../docs/modules/eips/pages/multicast-eip.adoc | 2 +- .../docs/modules/eips/pages/recipientList-eip.adoc | 334 ++++++++++----------- .../org/apache/camel/support/ObjectHelper.java | 1 - 3 files changed, 165 insertions(+), 172 deletions(-) diff --git a/core/camel-core-engine/src/main/docs/modules/eips/pages/multicast-eip.adoc b/core/camel-core-engine/src/main/docs/modules/eips/pages/multicast-eip.adoc index fe33060..bc18439 100644 --- a/core/camel-core-engine/src/main/docs/modules/eips/pages/multicast-eip.adoc +++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/multicast-eip.adoc @@ -29,7 +29,7 @@ The following properties are set on each Exchange that are multicasted: |======================================================================= | Property | Type | Description | `CamelMulticastIndex` | `int` | An index counter that increases for each Exchange being multicasted. The counter starts from 0. -| `MULTICAST_COMPLETE` | `boolean` |Whether or not this Exchange is the last. +| `CamelMulticastComplete` | `boolean` | Whether this Exchange is the last. |======================================================================= == Using Multicast 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 83d2abe..e0c6e13 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 @@ -5,11 +5,15 @@ :since: :supportlevel: Stable -image::eip/RecipientList.gif[image] +Camel supports the +https://www.enterpriseintegrationpatterns.com/RecipientList.html[Recipient List] +from the xref:enterprise-integration-patterns.adoc[EIP patterns]. -The recipients will receive a copy of the *same* Exchange, and Camel will execute them sequentially. +How do we route a message to a list of dynamically specified recipients? -TIP: See the `cacheSize` option for more details on _how much cache_ to use depending on how many or few unique endpoints are used. +image::eip/RecipientList.gif[image] + +Define a channel for each recipient. Then use a Recipient List to inspect an incoming message, determine the list of desired recipients, and forward the message to all channels associated with the recipients in the list. == Options @@ -18,39 +22,63 @@ include::partial$eip-options.adoc[] // eip options: END -[TIP] -==== -You can use the RecipientList Annotation on a POJO to create a Dynamic Recipient List. For more details see the Bean Integration. -==== +TIP: See the `cacheSize` option for more details on _how much cache_ to use depending on how many or few unique endpoints are used. + +== Exchange properties -== Static Recipient List -The following example shows how to route a request from an input *queue:a* endpoint to a static list of destinations +The following properties are set on each Exchange that are sent by the recipient list: + +[width="100%",cols="3,1m,6",options="header"] +|======================================================================= +| Property | Type | Description +| `CamelRecipientListEndpoint` | `String` | Uri of the `Endpoint` that the message was sent to. +|======================================================================= + +== Using Recipient List + +The Recipient List EIP allows to route *the same* message to a number of xref:latest@manual:ROOT:endpoint.adoc[endpoints] +and process them in a different way. + +There can be 1 or more destinations, and Camel will execute them sequentially (by default). +However, a parallel mode exists which allows processing messages concurrently. + +The Recipient List EIP has many features and is based on the xref:multicast-eip.adoc[Multicast] EIP. +For example the Recipient List EIP is capable of aggregating each message into a single +_response_ message as the result after the Recipient List EIP. + +=== Using static Recipient List + +The following example shows how to route a request from an input queue:a endpoint +to a static list of destinations, using `constant`: [source,java] ---- from("jms:queue:a") - .recipientList(constant("direct:b,direct:c,direct:d")); + .recipientList(constant("seda:x,seda:y,seda:z")); ---- And in XML: [source,xml] ---- -<camelContext xmlns="http://camel.apache.org/schema/spring"> - <route> - <from uri="jms:queue:a"/> - <recipientList> - <constant>direct:b,direct:c,direct:d</constant> - </recipientList> - </route> -</camelContext> +<route> + <from uri="jms:queue:a"/> + <recipientList> + <constant>seda:x,seda:y,seda:z</constant> + </recipientList> +</route> ---- -== Dynamic Recipient List +=== Using dynamic Recipient List + Usually one of the main reasons for using the Recipient List pattern is that the list of recipients is dynamic and -calculated at runtime. The following example demonstrates how to create a dynamic recipient list using an Expression -(which in this case extracts a named header value dynamically) to calculate the list of endpoints which are either -of type Endpoint or are converted to a String and then resolved using the endpoint URIs (separated by comma). +calculated at runtime. + +The following example demonstrates how to create a dynamic recipient list using an xref:latest@manual:ROOT:expression.adoc[Expression] +(which in this case extracts a named header value dynamically) to calculate the list of endpoints; which are either +of type `Endpoint` or are converted to a `String` and then resolved using the endpoint URIs (separated by comma). + +In Java DSL its as easy as: [source,java] ---- @@ -58,19 +86,34 @@ from("jms:queue:a") .recipientList(header("foo")); ---- -== Iteratable value +And in XML: + +[source,xml] +---- +<route> + <from uri="jms:queue:a"/> + <recipientList> + <header>foo</constant> + </recipientList> +</route> +---- + +==== How is dynamic destinations evaluated + The dynamic list of recipients that are defined in the header must be iterable such as: * `java.util.Collection` * `java.util.Iterator` * arrays * `org.w3c.dom.NodeList` -* a single String with values separated by comma (the delimiter configured) +* a single `String` with values separated by comma (the delimiter configured) * any other type will be regarded as a single value -== Using delimiter in Spring XML -In Spring 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. -By default Camel uses comma as delimiter, but this option lets you specify a custom delimiter to use instead. + +=== 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. +By default, Camel uses comma as delimiter, but this option lets you specify a custom delimiter to use instead. [source,xml] ---- @@ -83,19 +126,25 @@ By default Camel uses comma as delimiter, but this option lets you specify a cus </route> ---- -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, resulting into 3 endpoints to send to. -You can use spaces between the endpoints as Camel will trim the value when it lookup the endpoint to send to. +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, resulting into 3 endpoints to send to. +You can use spaces between the endpoints as Camel will trim the value when it look up the endpoint to send to. + +And in Java DSL, you specify the delimiter as 2nd parameter as shown below: -And in Java: [source,java] ---- from("direct:a") .recipientList(header("myHeader"), ";"); ---- -== Sending to multiple recipients in parallel -The Recipient List now supports `parallelProcessing` that for example Splitter also supports. -You can use it to use a thread pool to have concurrent tasks sending the Exchange to multiple recipients concurrently. +=== 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. +When using parallel processing then a thread pool is used to have concurrent tasks sending the `Exchange` +to multiple recipients concurrently. + +You can enable parallel mode using `parallelProcessing` in Java as shown: [source,java] ---- @@ -103,7 +152,7 @@ from("direct:a") .recipientList(header("myHeader")).parallelProcessing(); ---- -And in XML it is an attribute on the recipient list tag. +And in XML it is an attribute on `<recipientList>`: [source,xml] ---- @@ -115,39 +164,79 @@ And in XML it is an attribute on the recipient list tag. </route> ---- -=== Using custom thread pool +==== Using custom thread pool + A thread pool is only used for `parallelProcessing`. You supply your own custom thread pool via the `ExecutorServiceStrategy` (see Camel's Threading Model), the same way you would do it for the `aggregationStrategy`. By default Camel uses a thread pool with 10 threads (subject to change in future versions). -== Stop continuing in case one recipient failed -The Recipient List now supports `stopOnException` that for example Splitter also supports. -You can use it to stop sending to any further recipients in case any recipient failed. +The Multicast EIP will by default continue to process +the entire exchange even in case one of the +multicasted messages will throw an exception during routing. + +For example if you want to multicast to 3 destinations and the 2nd +destination fails by an exception. What Camel does by default is to +process the remainder destinations. You have the chance to deal with the exception +when aggregating using an `AggregationStrategy`. + +But sometimes you just want Camel to stop and let the exception be +propagated back, and let the Camel xref:latest@manual:ROOT:error-handler.adoc[Error Handler] +handle it. You can do this by specifying that it should stop in case of an +exception occurred. This is done by the `stopOnException` option as +shown below: [source,java] ---- -from("direct:a") - .recipientList(header("myHeader")).stopOnException(); +from("direct:start") + .recipientList(header("whereTo")).stopOnException() + .to("mock:result"); + + from("direct:foo").to("mock:foo"); + + from("direct:bar").process(new MyProcessor()).to("mock:bar"); + + from("direct:baz").to("mock:baz"); ---- -And in XML its an attribute on the recipient list tag. +In this example suppose a message is sent with the header `whereTo=direct:foo,direct:bar,direct:baz` +that means the recipient list sends messages to those 3 endpoints. + +Now suppose that the `MyProcessor` is causing a failure and throws an exception. +This means the Recipient List EIP will stop after this, and not the last route (direct:baz). + +And using XML DSL you specify it as follows: [source,xml] ---- -<route> - <from uri="direct:a"/> - <recipientList stopOnException="true"> - <header>myHeader</header> - </recipientList> -</route> +<routes> + <route> + <from uri="direct:start"/> + <recipientList stopOnException="true"> + <header>whereTo</header> + </recipientList> + <to uri="mock:result"/> + </route> + + <route> + <from uri="direct:foo"/> + <to uri="mock:foo"/> + </route> + + <route> + <from uri="direct:bar"/> + <process ref="myProcessor"/> + <to uri="mock:bar"/> + </route> + + <route> + <from uri="direct:baz"/> + <to uri="mock:baz"/> + </route> +</routes> ---- -[NOTE] -==== -You can combine parallelProcessing and stopOnException and have them both true. -==== +=== Ignore invalid endpoints -== Ignore invalid endpoints -The Recipient List now supports `ignoreInvalidEndpoints` (like the Routing Slip). +The Recipient List now supports `ignoreInvalidEndpoints` (like xref:routingSlip-eip.adoc[Routing Slip] EIP). You can use it to skip endpoints which are invalid. [source,java] @@ -156,7 +245,7 @@ from("direct:a") .recipientList(header("myHeader")).ignoreInvalidEndpoints(); ---- -And in XML it is an attribute on the recipient list tag. +And in XML it is an attribute on `<recipientList>`: [source,xml] ---- @@ -169,116 +258,24 @@ And in XML it is an attribute on the recipient list tag. ---- Then let us say the `myHeader` contains the following two endpoints `direct:foo,xxx:bar`. -The first endpoint is valid and works. However the second one is invalid and will just be ignored. +The first endpoint is valid and works. However, the second one is invalid and will just be ignored. Camel logs at DEBUG level about it, so you can see why the endpoint was invalid. -== Using custom AggregationStrategy -You can now use your own `AggregationStrategy` with the Recipient List. However this is rarely needed. -What it is good for is that in case you are using Request Reply messaging then the replies from the recipients can be aggregated. -By default Camel uses `UseLatestAggregationStrategy` which just keeps that last received reply. If you must remember all the bodies that all the recipients sent back, -then you can use your own custom aggregator that keeps those. It is the same principle as with the Aggregator EIP so check it out for details. +=== Using timeout -[source,java] ----- -from("direct:a") - .recipientList(header("myHeader")).aggregationStrategy(new MyOwnAggregationStrategy()) - .to("direct:b"); ----- +If you use `parallelProcessing` then you can configure a total `timeout` value in millis. -And in XML it is again an attribute on the recipient list tag. - -[source,xml] ----- -<route> - <from uri="direct:a"/> - <recipientList strategyRef="myStrategy"> - <header>myHeader</header> - </recipientList> - <to uri="direct:b"/> -</route> - -<!-- bean with the custom aggregation strategy --> -<bean id="myStrategy" class="com.mycompany.MyOwnAggregationStrategy"/> ----- - -[NOTE] -==== -The Multicast, Recipient List, and Splitter EIPs have special support for using `AggregationStrategy` with -access to the original input exchange. You may want to use this when you aggregate messages and -there has been a failure in one of the messages, which you then want to enrich on the original -input message and return as response; its the aggregate method with 3 exchange parameters. -==== - -== Knowing which endpoint when using custom AggregationStrategy - -When using a custom `AggregationStrategy` then the `aggregate` method is always invoked in sequential order -(also if parallel processing is enabled) of the endpoints the Recipient List is using. -However, Exchange has a property stored (key is `Exchange.RECIPIENT_LIST_ENDPOINT` with the uri of the Endpoint. -So you know which endpoint you are aggregating from. The code block shows how to access this property in your Aggregator. - -[source,java] ----- -@Override -public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { - String uri = newExchange.getProperty(Exchange.RECIPIENT_LIST_ENDPOINT, String.class); -} ----- - -== Using method call as recipient list -You can use a Bean to provide the recipients, for example: - -[source,java] ----- -from("activemq:queue:test") - .recipientList().method(MessageRouter.class, "routeTo"); ----- - -And then `MessageRouter` bean: - -[source,java] ----- -public class MessageRouter { - - public String routeTo() { - String queueName = "activemq:queue:test2"; - return queueName; - } -} ----- - -[CAUTION] -When you use a Bean then do *not* use the `@RecipientList` annotation as this will in fact add yet another recipient list, so you end up having two. Do *not* do the following: -[source,java] ----- -public class MessageRouter { - - // do not use recipientList in the Camel route calling a bean with the @RecipientList annotation! - @RecipientList - public String routeTo() { - String queueName = "activemq:queue:test2"; - return queueName; - } -} ----- - -You should only use the snippet above (using `@RecipientList`) if you just route to a Bean which you then want to act as a recipient list. -So the original route can be changed to: - -[source,java] ----- -from("activemq:queue:test").bean(MessageRouter.class, "routeTo"); ----- - -Which then would invoke the routeTo method and detect that it is annotated with `@RecipientList` and then act accordingly as if it was a recipient list EIP. - -== Using timeout -If you use `parallelProcessing` then you can configure a total `timeout` value in millis. Camel will then process the messages in parallel until the timeout is hit. This allows you to continue processing if one message consumer is slow. For example you can set a timeout value of 20 sec. +Camel will then process the messages in parallel until the timeout is hit. +This allows you to continue processing if one message consumer is slow. +For example, you can set a timeout value of 20 sec. [WARNING] .Tasks may keep running -If the timeout is reached with running tasks still remaining, certain tasks for which it is difficult for Camel to shut down in a graceful manner may continue to run. So use this option with a bit of care. We may be able to improve this functionality in future Camel releases. +If the timeout is reached with running tasks still remaining, certain tasks for which it is difficult for Camel to shut down in a graceful manner may continue to run. 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 3 destinations. We have a timeout of 2 seconds, 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". +For example in the unit test below you can see that we multicast the message to 3 destinations. +We have a timeout of 2 seconds, 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". [source,java] ---- @@ -306,13 +303,7 @@ from("direct:b").to("mock:B").setBody(constant("B")); from("direct:c").to("mock:C").setBody(constant("C")); ---- -[NOTE] -.Timeout in other EIPs -==== -This timeout feature is also supported by Splitter and both multicast and recipientList. -==== - -By default if a timeout occurs the `AggregationStrategy` is not invoked. However you can implement the `timeout` method: +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. [NOTE] @@ -322,13 +313,10 @@ The timeout is total, which means that after X time, Camel will aggregate the me The remainders will be cancelled. Camel will also only invoke the `timeout` method in the `TimeoutAwareAggregationStrategy` once, for the first index which caused the timeout. ==== -== Using onPrepare to execute custom logic when preparing messages -See details at the Multicast EIP - -== Using ExchangePattern in recipients +=== Using ExchangePattern in recipients The recipient list will by default use the current Exchange Pattern. Though one can imagine use-cases where one wants to send -a message to a recipient using a different exchange pattern. For example you may have a route that initiates as an `InOnly` route, +a message to a recipient using a different exchange pattern. For example, you may have a route that initiates as an `InOnly` route, but want to use `InOut` exchange pattern with a recipient list. You can configure the exchange pattern directly in the recipient endpoints. @@ -349,7 +337,13 @@ from("file:inbox") ==== The recipient list will not alter the original exchange pattern. So in the example above the exchange pattern will still be `InOnly` when the message is routed to the `file:outbox endpoint`. -If you want to alter the exchange pattern permanently then use the `.setExchangePattern` option. +If you want to alter the exchange pattern permanently then use `.setExchangePattern` in the route. -See more details at Request Reply and Event Message EIPs. +See more details at xref:event-message.adoc[Event Message] and xref:requestReply-eip.adoc[Request Reply] EIPs. ==== + +== See Also + +Because Recipient List EIP is based from the xref:multicast-eip.adoc[Multicast], +then you can find more information in xref:multicast-eip.adoc[Multicast] EIP +about features that is also available with Recipient List EIP. diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java index ae62c3b..c4f982b 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java @@ -660,7 +660,6 @@ public final class ObjectHelper { * Creates an iterable over the value if the value is a collection, an Object[], a String with values separated by * the given delimiter, or a primitive type array; otherwise to simplify the caller's code, we just create a * singleton collection iterator over a single value - * * </p> * In case of primitive type arrays the returned {@code Iterable} iterates over the corresponding Java primitive * wrapper objects of the given elements inside the {@code value} array. That's we get an autoboxing of the