Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractRoundRobinMessageSplitter |
|
| 0.0;0 | ||||
AbstractRoundRobinMessageSplitter$Counter |
|
| 0.0;0 |
1 | /* | |
2 | * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com | |
3 | * The software in this package is published under the terms of the CPAL v1.0 | |
4 | * license, a copy of which has been included with this distribution in the | |
5 | * LICENSE.txt file. | |
6 | */ | |
7 | package org.mule.routing.outbound; | |
8 | ||
9 | import org.mule.DefaultMuleMessage; | |
10 | import org.mule.api.MuleMessage; | |
11 | import org.mule.api.endpoint.OutboundEndpoint; | |
12 | import org.mule.api.lifecycle.InitialisationException; | |
13 | import org.mule.api.processor.MessageProcessor; | |
14 | import org.mule.config.i18n.CoreMessages; | |
15 | ||
16 | import java.util.HashMap; | |
17 | import java.util.Iterator; | |
18 | import java.util.LinkedList; | |
19 | import java.util.List; | |
20 | import java.util.Map; | |
21 | ||
22 | import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicInteger; | |
23 | ||
24 | /** | |
25 | * <code>FilteringListMessageSplitter</code> accepts a List as a message payload | |
26 | * then routes list elements as messages over an endpoint where the endpoint's filter | |
27 | * accepts the payload. | |
28 | */ | |
29 | 0 | public class AbstractRoundRobinMessageSplitter extends AbstractMessageSplitter |
30 | { | |
31 | 0 | private boolean deterministic = true; |
32 | ||
33 | /* Users should only disable this if they have filters configured on the endpoint | |
34 | * that will control which endpoint will receive the message | |
35 | */ | |
36 | 0 | private boolean disableRoundRobin = false; |
37 | ||
38 | 0 | private static final AtomicInteger globalCounter = new AtomicInteger(0); |
39 | 0 | private boolean failIfNoMatch = true; |
40 | ||
41 | @Override | |
42 | public void initialise() throws InitialisationException | |
43 | { | |
44 | 0 | if (isDisableRoundRobin()) |
45 | { | |
46 | 0 | setDeterministic(true); |
47 | } | |
48 | 0 | super.initialise(); |
49 | 0 | } |
50 | ||
51 | /** | |
52 | * Method used just to split the message into parts. Each part should be an entry in the list. | |
53 | * The list can contain either {@link org.mule.api.MuleMessage} objects or just payloads (Mule will | |
54 | * automatically convert the payloads into messages). | |
55 | * <p/> | |
56 | * This method can be overridden by custom implementations of splitter router where the distribution of | |
57 | * the message parts will be done using either round robin or endpoint filtering. | |
58 | * | |
59 | * @param message the source message to split into parts | |
60 | * @return a list of payload objects or {@link org.mule.api.MuleMessage} objects. Usually, it is sufficient | |
61 | * just to return payload objects | |
62 | */ | |
63 | protected List splitMessage(MuleMessage message) | |
64 | { | |
65 | 0 | if (message.getPayload() instanceof List) |
66 | { | |
67 | 0 | return new LinkedList((List) message.getPayload()); |
68 | } | |
69 | else | |
70 | { | |
71 | 0 | throw new IllegalArgumentException(CoreMessages.objectNotOfCorrectType( |
72 | message.getPayload().getClass(), List.class).getMessage()); | |
73 | } | |
74 | } | |
75 | ||
76 | /** | |
77 | * Retrieves a specific message part for the given endpoint. the message will then be | |
78 | * routed via the provider. <p/> <strong>NOTE:</strong>Implementations must provide | |
79 | * proper synchronization for shared state (payload, properties, etc.) | |
80 | * | |
81 | * @param message the current message being processed | |
82 | * @param endpoints A list of targets that will be used to dispatch each of the parts | |
83 | * @return a {@link java.util.List} of message parts. Each part will become the payload of the outgoing | |
84 | * message. Note that the parts will be dispatched to | |
85 | */ | |
86 | @Override | |
87 | protected SplitMessage getMessageParts(MuleMessage message, List<MessageProcessor> endpoints) | |
88 | { | |
89 | 0 | SplitMessage splitMessage = new SplitMessage(); |
90 | ||
91 | 0 | List payloads = splitMessage(message); |
92 | // Cache the properties here because for some message types getting the | |
93 | // properties can be expensive | |
94 | 0 | Map props = new HashMap(); |
95 | 0 | for (String propertyKey : message.getOutboundPropertyNames()) |
96 | { | |
97 | 0 | Object value = message.getOutboundProperty(propertyKey); |
98 | 0 | if (value != null) |
99 | { | |
100 | 0 | props.put(propertyKey, value); |
101 | } | |
102 | 0 | } |
103 | ||
104 | 0 | Counter counter = new Counter(); |
105 | ||
106 | 0 | for (Iterator iterator = payloads.iterator(); iterator.hasNext();) |
107 | { | |
108 | 0 | Object payload = iterator.next(); |
109 | 0 | MuleMessage part = new DefaultMuleMessage(payload, props, muleContext); |
110 | 0 | boolean matchFound = false; |
111 | ||
112 | // If there is no filter assume that the endpoint can accept the | |
113 | // message. Endpoints will be processed in order to only the last | |
114 | // (if any) of the the targets may not have a filter | |
115 | //Try each endpoint in the list. If there is no match for any of them we drop out and throw an exception | |
116 | 0 | for (int j = 0; j < endpoints.size(); j++) |
117 | { | |
118 | 0 | MessageProcessor target = endpoints.get(counter.next()); |
119 | 0 | OutboundEndpoint endpoint = target instanceof OutboundEndpoint ? (OutboundEndpoint) target : null; |
120 | 0 | if (endpoint == null || endpoint.getFilter() == null || endpoint.getFilter().accept(part)) |
121 | { | |
122 | 0 | if (logger.isDebugEnabled()) |
123 | { | |
124 | 0 | logger.debug("Endpoint filter matched. Routing message over: " |
125 | + endpoint.getEndpointURI().toString()); | |
126 | } | |
127 | 0 | iterator.remove(); |
128 | 0 | splitMessage.addPart(part, endpoint); |
129 | 0 | matchFound = true; |
130 | 0 | break; |
131 | } | |
132 | } | |
133 | 0 | if (!matchFound) |
134 | { | |
135 | 0 | if (isFailIfNoMatch()) |
136 | { | |
137 | 0 | throw new IllegalStateException(CoreMessages.splitMessageNoEndpointMatch(endpoints, payload).getMessage()); |
138 | } | |
139 | else | |
140 | { | |
141 | 0 | logger.info("No splitter match for message part. 'failIfNoMatch=false' ingoring message part."); |
142 | } | |
143 | } | |
144 | ||
145 | //start from 0 again | |
146 | 0 | if (isDisableRoundRobin()) |
147 | { | |
148 | 0 | counter = new Counter(); |
149 | } | |
150 | // if (enableCorrelation != ENABLE_CORRELATION_NEVER) | |
151 | // { | |
152 | // // always set correlation group size, even if correlation id | |
153 | // // has already been set (usually you don't have group size yet | |
154 | // // by this point. | |
155 | // final int groupSize = payload.size(); | |
156 | // message.setCorrelationGroupSize(groupSize); | |
157 | // if (logger.isDebugEnabled()) | |
158 | // { | |
159 | // logger.debug("java.util.List payload detected, setting correlation group size to " | |
160 | // + groupSize); | |
161 | // } | |
162 | // } | |
163 | 0 | } |
164 | 0 | return splitMessage; |
165 | ||
166 | } | |
167 | ||
168 | /** | |
169 | * If this option is true (the default) | |
170 | * then the first message part is routed to the first endpoint, the | |
171 | * second part to the second endpoint, etc, with the nth part going to | |
172 | * the (n modulo number of targets) endpoint. | |
173 | * If false then the messages will be distributed equally amongst all | |
174 | * targets. | |
175 | * <p/> | |
176 | * The behaviour changes if the targets have filters since the message part will get routed | |
177 | * based on the next endpoint that follows the above rule AND passes the endpoint filter. | |
178 | * | |
179 | * @return true if deterministic has been set to true | |
180 | */ | |
181 | public boolean isDeterministic() | |
182 | { | |
183 | 0 | return deterministic; |
184 | } | |
185 | ||
186 | /** | |
187 | * If this option is true (the default) | |
188 | * then the first message part is routed to the first endpoint, the | |
189 | * second part to the second endpoint, etc, with the nth part going to | |
190 | * the (n modulo number of targets) endpoint. | |
191 | * If false then the messages will be distributed equally amongst all | |
192 | * targets. | |
193 | * <p/> | |
194 | * The behaviour changes if the targets have filters since the message part will get routed | |
195 | * based on the next endpoint that follows the above rule AND passes the endpoint filter. | |
196 | * | |
197 | * @param deterministic the value to set | |
198 | */ | |
199 | public void setDeterministic(boolean deterministic) | |
200 | { | |
201 | 0 | this.deterministic = deterministic; |
202 | 0 | } |
203 | ||
204 | ||
205 | /** | |
206 | * The default behaviour for splitter routers is to round-robin across | |
207 | * targets. When using filters on targets it is sometimes desirable to use only the filters to | |
208 | * control which endpoint the split message part goes too. For example, if you have 3 targets where | |
209 | * two have a filter but the last does not, you'll need to disable round robin since the 3rd endpoint | |
210 | * may end up routing a message that one of the other targets should have routed. | |
211 | * Generally it is good practice to either configure all targets with filters or none, in this case | |
212 | * there is not need to set this property. | |
213 | * | |
214 | * @return true if disabled | |
215 | */ | |
216 | public boolean isDisableRoundRobin() | |
217 | { | |
218 | 0 | return disableRoundRobin; |
219 | } | |
220 | ||
221 | /** | |
222 | * The default behaviour for splitter routers is to round-robin across | |
223 | * targets. When using filters on targets it is sometimes desirable to use only the filters to | |
224 | * control which endpoint the split message part goes too. For example, if you have 3 targets where | |
225 | * two have a filter but the last does not, you'll need to disable round robin since the 3rd endpoint | |
226 | * may end up routing a message that one of the other targets should have routed. | |
227 | * Generally it is good practice to either configure all targets with filters or none, in this case | |
228 | * there is not need to set this property. | |
229 | * | |
230 | * @param disableRoundRobin true if disabled | |
231 | */ | |
232 | public void setDisableRoundRobin(boolean disableRoundRobin) | |
233 | { | |
234 | 0 | this.disableRoundRobin = disableRoundRobin; |
235 | 0 | } |
236 | ||
237 | /** | |
238 | * If none of the targets match a split message part i.e. each endpoint has a | |
239 | * filter for a certain message part. This flag controls whether the part is ignorred or an | |
240 | * exceptin is thrown. | |
241 | * | |
242 | * @return true if an exception should be thrown when no match is found | |
243 | */ | |
244 | public boolean isFailIfNoMatch() | |
245 | { | |
246 | 0 | return failIfNoMatch; |
247 | } | |
248 | ||
249 | /** | |
250 | * If none of the targets match a split message part i.e. each endpoint has a | |
251 | * filter for a certain message part. This flag controls whether the part is ignorred or an | |
252 | * exceptin is thrown. | |
253 | * | |
254 | * @param failIfNoMatch true if an exception should be thrown when no match is found | |
255 | */ | |
256 | public void setFailIfNoMatch(boolean failIfNoMatch) | |
257 | { | |
258 | 0 | this.failIfNoMatch = failIfNoMatch; |
259 | 0 | } |
260 | ||
261 | 0 | private class Counter |
262 | { | |
263 | ||
264 | private AtomicInteger counter; | |
265 | ||
266 | public Counter() | |
267 | 0 | { |
268 | 0 | if (isDeterministic()) |
269 | { | |
270 | 0 | counter = new AtomicInteger(0); |
271 | } | |
272 | else | |
273 | { | |
274 | 0 | counter = globalCounter; |
275 | } | |
276 | 0 | } |
277 | ||
278 | public int next() | |
279 | { | |
280 | 0 | return counter.getAndIncrement() % getRoutes().size(); |
281 | } | |
282 | ||
283 | } | |
284 | } |