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