View Javadoc

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