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