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.transport;
8   
9   import org.mule.api.MuleException;
10  import org.mule.api.construct.FlowConstruct;
11  import org.mule.api.endpoint.InboundEndpoint;
12  import org.mule.api.lifecycle.CreateException;
13  import org.mule.api.transport.Connector;
14  import org.mule.config.i18n.CoreMessages;
15  import org.mule.util.ObjectUtils;
16  
17  import java.util.HashMap;
18  import java.util.Iterator;
19  import java.util.Map;
20  
21  import edu.emory.mathcs.backport.java.util.concurrent.Future;
22  import edu.emory.mathcs.backport.java.util.concurrent.RejectedExecutionException;
23  import edu.emory.mathcs.backport.java.util.concurrent.ScheduledExecutorService;
24  import edu.emory.mathcs.backport.java.util.concurrent.ScheduledFuture;
25  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
26  
27  /**
28   * <code>AbstractPollingMessageReceiver</code> implements a base class for polling
29   * message receivers. The receiver provides a {@link #poll()} method that implementations
30   * must implement to execute their custom code. Note that the receiver will not poll if
31   * the associated connector is not started.
32   */
33  public abstract class AbstractPollingMessageReceiver extends AbstractMessageReceiver
34  {
35      public static final long DEFAULT_POLL_FREQUENCY = 1000;
36      public static final TimeUnit DEFAULT_POLL_TIMEUNIT = TimeUnit.MILLISECONDS;
37  
38      public static final long DEFAULT_STARTUP_DELAY = 1000;
39  
40      private long frequency = DEFAULT_POLL_FREQUENCY;
41      private TimeUnit timeUnit = DEFAULT_POLL_TIMEUNIT;
42  
43      // @GuardedBy(itself)
44      protected final Map<ScheduledFuture, PollingReceiverWorker> schedules = new HashMap<ScheduledFuture, PollingReceiverWorker>();
45  
46      public AbstractPollingMessageReceiver(Connector connector,
47                                            FlowConstruct flowConstruct,
48                                            final InboundEndpoint endpoint) throws CreateException
49      {
50          super(connector, flowConstruct, endpoint);
51      }
52  
53      @Override
54      protected void doStart() throws MuleException
55      {
56          try
57          {
58              this.schedule();
59          }
60          catch (Exception ex)
61          {
62              this.stop();
63              throw new CreateException(CoreMessages.failedToScheduleWork(), ex, this);
64          }
65      }
66  
67      @Override
68      protected void doStop() throws MuleException
69      {
70          this.unschedule();
71      }
72  
73      /**
74       * This method registers this receiver for periodic polling ticks with the connectors
75       * scheduler. Subclasses can override this in case they want to handle their polling
76       * differently.
77       *
78       * @throws RejectedExecutionException
79       * @throws NullPointerException
80       * @throws IllegalArgumentException
81       * @see ScheduledExecutorService#scheduleWithFixedDelay(Runnable, long, long, TimeUnit)
82       */
83      protected void schedule()
84              throws RejectedExecutionException, NullPointerException, IllegalArgumentException
85      {
86          synchronized (schedules)
87          {
88              // we use scheduleWithFixedDelay to prevent queue-up of tasks when
89              // polling takes longer than the specified frequency, e.g. when the
90              // polled database or network is slow or returns large amounts of
91              // data.
92              PollingReceiverWorker pollingReceiverWorker = this.createWork();
93              ScheduledFuture schedule = connector.getScheduler().scheduleWithFixedDelay(
94                      new PollingReceiverWorkerSchedule(pollingReceiverWorker), DEFAULT_STARTUP_DELAY,
95                      this.getFrequency(), this.getTimeUnit());
96              schedules.put(schedule, pollingReceiverWorker);
97  
98              if (logger.isDebugEnabled())
99              {
100                 logger.debug(ObjectUtils.identityToShortString(this) + " scheduled "
101                              + ObjectUtils.identityToShortString(schedule) + " with " + frequency
102                              + " " + getTimeUnit() + " polling frequency");
103             }
104         }
105     }
106 
107     /**
108      * This method cancels the schedules which were created in {@link #schedule()}.
109      *
110      * @see Future#cancel(boolean)
111      */
112     protected void unschedule()
113     {
114         synchronized (schedules)
115         {
116             // cancel our schedules gently: do not interrupt when polling is in progress
117             for (Iterator<ScheduledFuture> i = schedules.keySet().iterator(); i.hasNext();)
118             {
119                 ScheduledFuture schedule = i.next();
120                 schedule.cancel(false);
121                 // Wait until in-progress PollingRecevierWorker completes.
122                 int shutdownTimeout = connector.getMuleContext().getConfiguration().getShutdownTimeout();
123                 PollingReceiverWorker worker = schedules.get(schedule);
124                 for (int elapsed = 0; worker.isRunning() && elapsed < shutdownTimeout; elapsed += 50)
125                 {
126                     try
127                     {
128                         Thread.sleep(50);
129                     }
130                     catch (InterruptedException e)
131                     {
132                         logger.warn(
133                                 ObjectUtils.identityToShortString(this) + "  interrupted while waiting for poll() to complete as part of message receiver stop.",
134                                 e);
135                         break;
136                     }
137                 }
138                 i.remove();
139 
140                 if (logger.isDebugEnabled())
141                 {
142                     logger.debug(ObjectUtils.identityToShortString(this) + " cancelled polling schedule: "
143                                  + ObjectUtils.identityToShortString(schedule));
144                 }
145             }
146         }
147     }
148 
149     public void disableNativeScheduling()
150     {
151         this.unschedule();
152     }
153 
154     protected PollingReceiverWorker createWork()
155     {
156         return new PollingReceiverWorker(this);
157     }
158 
159     public long getFrequency()
160     {
161         return frequency;
162     }
163 
164     // TODO a nifty thing would be on-the-fly adjustment (via JMX?) of the
165     // polling frequency by rescheduling without explicit stop()
166     public void setFrequency(long value)
167     {
168         if (value <= 0)
169         {
170             frequency = DEFAULT_POLL_FREQUENCY;
171         }
172         else
173         {
174             frequency = value;
175         }
176     }
177 
178     public TimeUnit getTimeUnit()
179     {
180         return timeUnit;
181     }
182 
183     public void setTimeUnit(TimeUnit timeUnit)
184     {
185         this.timeUnit = timeUnit;
186     }
187     
188     /**
189      * The preferred number of messages to process in the current batch. We need to
190      * drain the queue quickly, but not by slamming the workManager too hard. It is
191      * impossible to determine this more precisely without proper load
192      * statistics/feedback or some kind of "event cost estimate". Therefore we just
193      * try to use half of the receiver's workManager, since it is shared with
194      * receivers for other endpoints. TODO make this user-settable
195      * 
196      * @param available the number if messages currently available to be processed
197      */
198     protected int getBatchSize(int available)
199     {
200         if (available <= 0)
201         {
202             return 0;
203         }
204 
205         int maxThreads = connector.getReceiverThreadingProfile().getMaxThreadsActive();
206         return Math.max(1, Math.min(available, ((maxThreads / 2) - 1)));
207     }
208 
209     public abstract void poll() throws Exception;
210 
211 }