View Javadoc
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 }