View Javadoc

1   /*
2    * $Id: VMMessageReceiver.java 19919 2010-10-14 21:11:34Z 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.vm;
12  
13  import org.mule.DefaultMuleMessage;
14  import org.mule.api.DefaultMuleException;
15  import org.mule.api.MuleEvent;
16  import org.mule.api.MuleException;
17  import org.mule.api.MuleMessage;
18  import org.mule.api.construct.FlowConstruct;
19  import org.mule.api.endpoint.InboundEndpoint;
20  import org.mule.api.lifecycle.CreateException;
21  import org.mule.api.transport.Connector;
22  import org.mule.api.transport.PropertyScope;
23  import org.mule.transport.PollingReceiverWorker;
24  import org.mule.transport.TransactedPollingMessageReceiver;
25  import org.mule.util.queue.Queue;
26  import org.mule.util.queue.QueueSession;
27  
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.Map;
33  
34  import javax.activation.DataHandler;
35  
36  import edu.emory.mathcs.backport.java.util.concurrent.RejectedExecutionException;
37  
38  /**
39   * <code>VMMessageReceiver</code> is a listener for events from a Mule service which then simply passes
40   * the events on to the target service.
41   */
42  public class VMMessageReceiver extends TransactedPollingMessageReceiver
43  {
44  
45      private VMConnector connector;
46  
47      public VMMessageReceiver(Connector connector, FlowConstruct flowConstruct, InboundEndpoint endpoint)
48          throws CreateException
49      {
50          super(connector, flowConstruct, endpoint);
51          this.setReceiveMessagesInTransaction(endpoint.getTransactionConfig().isTransacted());
52          this.connector = (VMConnector) connector;
53      }
54  
55      /*
56       * We only need to start scheduling this receiver if event queueing is enabled on the connector; otherwise
57       * events are delivered via onEvent/onCall.
58       */
59      @Override
60      protected void schedule() throws RejectedExecutionException, NullPointerException, IllegalArgumentException
61      {
62          if (!endpoint.getExchangePattern().hasResponse())
63          {
64              super.schedule();
65          }
66      }
67  
68      @Override
69      protected void doDispose()
70      {
71          // template method
72      }
73  
74      @Override
75      protected void doConnect() throws Exception
76      {
77          if (!endpoint.getExchangePattern().hasResponse())
78          {
79              // Ensure we can create a vm queue
80              QueueSession queueSession = connector.getQueueSession();
81              Queue q = queueSession.getQueue(endpoint.getEndpointURI().getAddress());
82              if (logger.isDebugEnabled())
83              {
84                  logger.debug("Current queue depth for queue: " + endpoint.getEndpointURI().getAddress() + " is: "
85                               + q.size());
86              }
87          }
88      }
89  
90      @Override
91      protected void doDisconnect() throws Exception
92      {
93          // template method
94      }
95  
96      public void onMessage(MuleMessage message) throws MuleException
97      {
98          // Rewrite the message to treat it as a new message
99          MuleMessage newMessage = new DefaultMuleMessage(message.getPayload(), message, connector.getMuleContext());
100         routeMessage(newMessage);
101     }
102 
103     public MuleMessage onCall(MuleMessage message, boolean synchronous) throws MuleException
104     {
105         try
106         {
107             // Rewrite the message to treat it as a new message
108             MuleMessage newMessage = message.createInboundMessage();
109             MuleEvent event =  routeMessage(newMessage);
110             MuleMessage returnedMessage = event == null ? null : event.getMessage();
111             if (returnedMessage != null)
112             {
113                 returnedMessage = returnedMessage.createInboundMessage();
114             }
115             return returnedMessage;
116         }
117         catch (Exception e)
118         {
119             throw new DefaultMuleException(e);
120         }
121     }
122 
123     /**
124      * It's impossible to process all messages in the receive transaction
125      */
126     @Override
127     protected List<MuleMessage> getMessages() throws Exception
128     {
129         if (isReceiveMessagesInTransaction())
130         {
131             MuleEvent message = getFirstMessage();
132             if (message == null)
133             {
134                 return null;
135             }
136             
137             List<MuleMessage> messages = new ArrayList<MuleMessage>(1);
138             messages.add(message.getMessage());
139             return messages;
140         }
141         else
142         {
143             return getFirstMessages();
144         }
145     }
146     
147     protected List<MuleMessage> getFirstMessages() throws Exception
148     {
149         // The queue from which to pull events
150         QueueSession qs = connector.getQueueSession();
151         Queue queue = qs.getQueue(endpoint.getEndpointURI().getAddress());
152 
153         // The list of retrieved messages that will be returned
154         List<MuleMessage> messages = new LinkedList<MuleMessage>();
155 
156         /*
157          * Determine how many messages to batch in this poll: we need to drain the queue quickly, but not by
158          * slamming the workManager too hard. It is impossible to determine this more precisely without proper
159          * load statistics/feedback or some kind of "event cost estimate". Therefore we just try to use half
160          * of the receiver's workManager, since it is shared with receivers for other endpoints.
161          */
162         int maxThreads = connector.getReceiverThreadingProfile().getMaxThreadsActive();
163         // also make sure batchSize is always at least 1
164         int batchSize = Math.max(1, Math.min(queue.size(), ((maxThreads / 2) - 1)));
165 
166         // try to get the first event off the queue
167         MuleEvent message = (MuleEvent) queue.poll(connector.getQueueTimeout());
168 
169         if (message != null)
170         {
171             // keep first dequeued event
172             messages.add(message.getMessage());
173 
174             // keep batching if more events are available
175             for (int i = 0; i < batchSize && message != null; i++)
176             {
177                 message = (MuleEvent)queue.poll(0);
178                 if (message != null)
179                 {
180                     messages.add(message.getMessage());
181                 }
182             }
183         }
184 
185         // let our workManager handle the batch of events
186         return messages;
187     }
188     
189     protected MuleEvent getFirstMessage() throws Exception
190     {
191         // The queue from which to pull events
192         QueueSession qs = connector.getQueueSession();
193         Queue queue = qs.getQueue(endpoint.getEndpointURI().getAddress());
194         // try to get the first event off the queue
195         return (MuleEvent) queue.poll(connector.getQueueTimeout());
196     }
197 
198     @Override
199     protected void processMessage(Object msg) throws Exception
200     {
201         MuleMessage message = (MuleMessage) msg;
202 
203         // Rewrite the message to treat it as a new message
204         MuleMessage newMessage = message.createInboundMessage();
205         routeMessage(newMessage);
206     }
207 
208     /*
209      * We create our own "polling" worker here since we need to evade the standard scheduler.
210      */
211     @Override
212     protected PollingReceiverWorker createWork()
213     {
214         return new VMReceiverWorker(this);
215     }
216     
217     /*
218      * Even though the VM transport is "polling" for messages, the nonexistent cost of accessing the queue is
219      * a good reason to not use the regular scheduling mechanism in order to both minimize latency and
220      * maximize throughput.
221      */
222     protected static class VMReceiverWorker extends PollingReceiverWorker
223     {
224 
225         public VMReceiverWorker(VMMessageReceiver pollingMessageReceiver)
226         {
227             super(pollingMessageReceiver);
228         }
229 
230         @Override
231         public void run()
232         {
233             /*
234              * We simply run our own polling loop all the time as long as the receiver is started. The
235              * blocking wait defined by VMConnector.getQueueTimeout() will prevent this worker's receiver
236              * thread from busy-waiting.
237              */
238             while (this.getReceiver().isConnected())
239             {
240                 super.run();
241             }
242         }
243     }
244 
245 }