1
2
3
4
5
6
7
8
9
10
11 package org.mule.transport.jms;
12
13 import org.mule.api.MuleMessage;
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.transaction.Transaction;
18 import org.mule.api.transaction.TransactionCallback;
19 import org.mule.api.transport.Connector;
20 import org.mule.retry.policies.NoRetryPolicyTemplate;
21 import org.mule.transaction.TransactionCoordination;
22 import org.mule.transaction.TransactionTemplate;
23 import org.mule.transaction.XaTransaction;
24 import org.mule.transport.ConnectException;
25 import org.mule.transport.TransactedPollingMessageReceiver;
26 import org.mule.transport.jms.filters.JmsSelectorFilter;
27 import org.mule.transport.jms.redelivery.RedeliveryHandler;
28 import org.mule.util.ClassUtils;
29 import org.mule.util.MapUtils;
30
31 import java.util.List;
32
33 import javax.jms.Destination;
34 import javax.jms.JMSException;
35 import javax.jms.Message;
36 import javax.jms.MessageConsumer;
37 import javax.jms.Session;
38
39 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
40 import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicReference;
41
42 public class XaTransactedJmsMessageReceiver extends TransactedPollingMessageReceiver
43 {
44 public static final long DEFAULT_JMS_POLL_FREQUENCY = 100;
45 public static final TimeUnit DEFAULT_JMS_POLL_TIMEUNIT = TimeUnit.MILLISECONDS;
46
47 protected final JmsConnector connector;
48 protected boolean reuseConsumer;
49 protected boolean reuseSession;
50 protected final ThreadContextLocal context = new ThreadContextLocal();
51 protected final long timeout;
52 private final AtomicReference
53
54
55
56
57 protected static class JmsThreadContext
58 {
59 public Session session;
60 public MessageConsumer consumer;
61 }
62
63
64
65
66 protected static class ThreadContextLocal extends ThreadLocal
67 {
68 public JmsThreadContext getContext()
69 {
70 return (JmsThreadContext)get();
71 }
72
73 @Override
74 protected Object initialValue()
75 {
76 return new JmsThreadContext();
77 }
78 }
79
80 public XaTransactedJmsMessageReceiver(Connector connector, FlowConstruct flowConstruct, InboundEndpoint endpoint)
81 throws CreateException
82 {
83 super(connector, flowConstruct, endpoint);
84
85
86 this.setTimeUnit(DEFAULT_JMS_POLL_TIMEUNIT);
87 this.setFrequency(MapUtils.getLongValue(endpoint.getProperties(), "pollingFrequency", DEFAULT_JMS_POLL_FREQUENCY));
88
89 this.connector = (JmsConnector) connector;
90 this.timeout = endpoint.getTransactionConfig().getTimeout();
91
92
93
94
95 if (retryTemplate != null && !(retryTemplate instanceof NoRetryPolicyTemplate))
96 {
97 this.reuseConsumer = false;
98 this.reuseSession = false;
99 }
100
101
102
103 this.reuseConsumer = MapUtils.getBooleanValue(endpoint.getProperties(), "reuseConsumer",
104 this.reuseConsumer);
105 this.reuseSession = MapUtils.getBooleanValue(endpoint.getProperties(), "reuseSession",
106 this.reuseSession);
107
108
109 boolean topic = this.connector.getTopicResolver().isTopic(getEndpoint());
110 if (topic && (reuseConsumer || reuseSession))
111 {
112 logger.warn("Destination " + getEndpoint().getEndpointURI() + " is a topic and XA transaction was " +
113 "configured. Forcing 'reuseSession' and 'reuseConsumer' to false. Set these " +
114 "on endpoint to avoid the message.");
115 reuseConsumer = false;
116 reuseSession = false;
117 }
118
119
120
121
122
123
124
125
126 this.setUseMultipleTransactedReceivers(!topic);
127 }
128
129 @Override
130 protected void doDispose()
131 {
132
133 }
134
135 @Override
136 protected void doConnect() throws Exception
137 {
138 if (redeliveryHandler.compareAndSet(null, connector.getRedeliveryHandlerFactory().create()))
139 {
140 ((RedeliveryHandler) redeliveryHandler.get()).setConnector(this.connector);
141 }
142 }
143
144 @Override
145 protected void doDisconnect() throws Exception
146 {
147 if (connector.isConnected())
148 {
149
150 closeResource(true);
151 }
152 }
153
154
155
156
157 @Override
158 public void poll() throws Exception
159 {
160 logger.debug("Polling...");
161
162 TransactionTemplate<Void> tt = new TransactionTemplate<Void>(
163 endpoint.getTransactionConfig(),
164 connector.getMuleContext());
165 TransactionCallback<Void> cb = new TransactionCallback<Void>()
166 {
167 public Void doInTransaction() throws Exception
168 {
169 try
170 {
171 List messages = getMessages();
172 if (messages != null && messages.size() > 0)
173 {
174 for (Object message : messages)
175 {
176 processMessage(message);
177 }
178 }
179 return null;
180 }
181 catch (Exception e)
182 {
183
184
185 JmsThreadContext ctx = context.getContext();
186 ctx.consumer = null;
187 Transaction tx = TransactionCoordination.getInstance().getTransaction();
188 if (ctx.session != null && tx instanceof XaTransaction.MuleXaObject)
189 {
190 if (ctx.session instanceof XaTransaction.MuleXaObject)
191 {
192 ((XaTransaction.MuleXaObject) ctx.session).setReuseObject(false);
193 }
194 else
195 {
196 logger.warn("Session should be XA, but is of type " + ctx.session.getClass().getName());
197 }
198 }
199 ctx.session = null;
200 throw e;
201 }
202 }
203 };
204
205 tt.execute(cb);
206 }
207
208 @Override
209 protected List<MuleMessage> getMessages() throws Exception
210 {
211 Session session = this.connector.getSessionFromTransaction();
212 Transaction tx = TransactionCoordination.getInstance().getTransaction();
213 MessageConsumer consumer = createConsumer();
214
215
216 Message message = null;
217 try
218 {
219 message = consumer.receive(timeout);
220 }
221 catch (JMSException e)
222 {
223
224 if (!this.isConnected())
225 {
226
227 }
228 else
229 {
230 throw e;
231 }
232 }
233
234 if (message == null)
235 {
236 if (tx != null)
237 {
238 tx.setRollbackOnly();
239 }
240 return null;
241 }
242 message = connector.preProcessMessage(message, session);
243
244
245 if (logger.isDebugEnabled())
246 {
247 logger.debug("Message received it is of type: " +
248 ClassUtils.getSimpleName(message.getClass()));
249 if (message.getJMSDestination() != null)
250 {
251 logger.debug("Message received on " + message.getJMSDestination() + " ("
252 + message.getJMSDestination().getClass().getName() + ")");
253 }
254 else
255 {
256 logger.debug("Message received on unknown destination");
257 }
258 logger.debug("Message CorrelationId is: " + message.getJMSCorrelationID());
259 logger.debug("Jms Message Id is: " + message.getJMSMessageID());
260 }
261
262 if (message.getJMSRedelivered())
263 {
264 if (logger.isDebugEnabled())
265 {
266 logger.debug("Message with correlationId: " + message.getJMSCorrelationID()
267 + " is redelivered. handing off to Exception Handler");
268 }
269 ((RedeliveryHandler) redeliveryHandler.get()).handleRedelivery(message, endpoint, flowConstruct);
270 }
271
272 MuleMessage messageToRoute = createMuleMessage(message, endpoint.getEncoding());
273 routeMessage(messageToRoute);
274 return null;
275 }
276
277 @Override
278 protected void processMessage(Object msg) throws Exception
279 {
280
281
282 }
283
284
285
286
287 protected void closeResource(boolean force)
288 {
289 JmsThreadContext ctx = context.getContext();
290 if (ctx == null)
291 {
292 return;
293 }
294
295
296 if (force || !reuseSession || !reuseConsumer)
297 {
298 connector.closeQuietly(ctx.consumer);
299 ctx.consumer = null;
300 }
301
302
303
304 if (force || !reuseSession)
305 {
306 connector.closeQuietly(ctx.session);
307 ctx.session = null;
308 }
309 }
310
311
312
313
314
315
316 protected MessageConsumer createConsumer() throws Exception
317 {
318 logger.debug("Create a consumer for the jms destination");
319 try
320 {
321 JmsSupport jmsSupport = this.connector.getJmsSupport();
322
323 JmsThreadContext ctx = context.getContext();
324 if (ctx == null)
325 {
326 ctx = new JmsThreadContext();
327 }
328
329 Session session;
330 Transaction tx = TransactionCoordination.getInstance().getTransaction();
331 if (this.reuseSession && ctx.session != null)
332 {
333 session = ctx.session;
334 tx.bindResource(this.connector.getConnection(), session);
335 }
336 else
337 {
338 session = this.connector.getSession(endpoint);
339 if (session != null && tx != null)
340 {
341 if (session instanceof XaTransaction.MuleXaObject)
342 {
343 ((XaTransaction.MuleXaObject) session).setReuseObject(reuseSession);
344 }
345 else
346 {
347 logger.warn("Session should be XA, but is of type " + session.getClass().getName());
348 }
349 }
350 }
351
352 if (reuseSession)
353 {
354 ctx.session = session;
355 }
356
357
358 if (this.reuseConsumer && ctx.consumer != null)
359 {
360 return ctx.consumer;
361 }
362
363
364 final boolean topic = connector.getTopicResolver().isTopic(endpoint);
365 Destination dest = jmsSupport.createDestination(session, endpoint);
366
367
368 String selector = null;
369 if (endpoint.getFilter() != null && endpoint.getFilter() instanceof JmsSelectorFilter)
370 {
371 selector = ((JmsSelectorFilter)endpoint.getFilter()).getExpression();
372 }
373 else if (endpoint.getProperties() != null)
374 {
375
376
377 selector = (String)endpoint.getProperties().get(JmsConstants.JMS_SELECTOR_PROPERTY);
378 }
379 String tempDurable = (String)endpoint.getProperties().get("durable");
380 boolean durable = connector.isDurable();
381 if (tempDurable != null)
382 {
383 durable = Boolean.valueOf(tempDurable);
384 }
385
386
387 String durableName = (String)endpoint.getProperties().get("durableName");
388 if (durableName == null && durable && topic)
389 {
390 durableName = "mule." + connector.getName() + "." + endpoint.getEndpointURI().getAddress();
391 logger.debug("Jms Connector for this receiver is durable but no durable name has been specified. Defaulting to: "
392 + durableName);
393 }
394
395
396 MessageConsumer consumer = jmsSupport.createConsumer(session, dest, selector, connector.isNoLocal(),
397 durableName, topic, endpoint);
398 if (reuseConsumer)
399 {
400 ctx.consumer = consumer;
401 }
402 return consumer;
403 }
404 catch (JMSException e)
405 {
406 throw new ConnectException(e, this);
407 }
408 }
409 }