View Javadoc

1   /*
2    * $Id: AbstractRoundRobinMessageSplitter.java 19191 2010-08-25 21:05:23Z tcarlson $
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 }