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