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