View Javadoc

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 }