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 public class AbstractRoundRobinMessageSplitter extends AbstractMessageSplitter 34 { 35 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 private boolean disableRoundRobin = false; 41 42 private static final AtomicInteger globalCounter = new AtomicInteger(0); 43 private boolean failIfNoMatch = true; 44 45 @Override 46 public void initialise() throws InitialisationException 47 { 48 if (isDisableRoundRobin()) 49 { 50 setDeterministic(true); 51 } 52 super.initialise(); 53 } 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 if (message.getPayload() instanceof List) 70 { 71 return new LinkedList((List) message.getPayload()); 72 } 73 else 74 { 75 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 SplitMessage splitMessage = new SplitMessage(); 94 95 List payloads = splitMessage(message); 96 // Cache the properties here because for some message types getting the 97 // properties can be expensive 98 Map props = new HashMap(); 99 for (String propertyKey : message.getOutboundPropertyNames()) 100 { 101 Object value = message.getOutboundProperty(propertyKey); 102 if (value != null) 103 { 104 props.put(propertyKey, value); 105 } 106 } 107 108 Counter counter = new Counter(); 109 110 for (Iterator iterator = payloads.iterator(); iterator.hasNext();) 111 { 112 Object payload = iterator.next(); 113 MuleMessage part = new DefaultMuleMessage(payload, props, muleContext); 114 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 for (int j = 0; j < endpoints.size(); j++) 121 { 122 MessageProcessor target = endpoints.get(counter.next()); 123 OutboundEndpoint endpoint = target instanceof OutboundEndpoint ? (OutboundEndpoint) target : null; 124 if (endpoint == null || endpoint.getFilter() == null || endpoint.getFilter().accept(part)) 125 { 126 if (logger.isDebugEnabled()) 127 { 128 logger.debug("Endpoint filter matched. Routing message over: " 129 + endpoint.getEndpointURI().toString()); 130 } 131 iterator.remove(); 132 splitMessage.addPart(part, endpoint); 133 matchFound = true; 134 break; 135 } 136 } 137 if (!matchFound) 138 { 139 if (isFailIfNoMatch()) 140 { 141 throw new IllegalStateException(CoreMessages.splitMessageNoEndpointMatch(endpoints, payload).getMessage()); 142 } 143 else 144 { 145 logger.info("No splitter match for message part. 'failIfNoMatch=false' ingoring message part."); 146 } 147 } 148 149 //start from 0 again 150 if (isDisableRoundRobin()) 151 { 152 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 } 168 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 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 this.deterministic = deterministic; 206 } 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 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 this.disableRoundRobin = disableRoundRobin; 239 } 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 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 this.failIfNoMatch = failIfNoMatch; 263 } 264 265 private class Counter 266 { 267 268 private AtomicInteger counter; 269 270 public Counter() 271 { 272 if (isDeterministic()) 273 { 274 counter = new AtomicInteger(0); 275 } 276 else 277 { 278 counter = globalCounter; 279 } 280 } 281 282 public int next() 283 { 284 return counter.getAndIncrement() % getRoutes().size(); 285 } 286 287 } 288 }