forked from seam2/jboss-seam
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Jms.xml
executable file
·618 lines (512 loc) · 23.7 KB
/
Jms.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
<chapter id="jms">
<title>Asynchronicity and messaging</title>
<para>
Seam makes it very easy to perform work asynchronously from a web request. When most people
think of asynchronicity in Java EE, they think of using JMS. This is certainly one way to
approach the problem in Seam, and is the right way when you have strict and well-defined
quality of service requirements. Seam makes it easy to send and receive JMS messages using
Seam components.
</para>
<para>
But for cases when you are simply want to use a worker thread, JMS is overkill. Seam layers a simple
asynchronous method and event facility over your choice of <emphasis>dispatchers</emphasis>:
</para>
<itemizedlist>
<listitem>
<para>
<literal>java.util.concurrent.ScheduledThreadPoolExecutor</literal> (by default)
</para>
</listitem>
<listitem>
<para>
the EJB timer service (for EJB 3.0 environments)
</para>
</listitem>
<listitem>
<para>
Quartz
</para>
</listitem>
</itemizedlist>
<para>
This chapter first covers how to leverage Seam to simplify JMS and then explains how to use the
simpler asynchronous method and event facility.
</para>
<sect1>
<title>Messaging in Seam</title>
<para>
Seam makes it easy to send and receive JMS messages to and from
Seam components. Both the message publisher and the message receiver
can be Seam components.
</para>
<para>
You'll first learn to setup a queue and topic message publisher and then
look at an example that illustrates how to perform the message exchange.
</para>
<sect2>
<title>Configuration</title>
<para>
To configure Seam's infrastructure for sending JMS messages,
you need to tell Seam about any topics and queues you want to
send messages to, and also tell Seam where to find the
<literal>QueueConnectionFactory</literal> and/or
<literal>TopicConnectionFactory</literal>.
</para>
<para>
Seam defaults to using <literal>UIL2ConnectionFactory</literal>
which is the usual connection factory for use with JBossMQ. If
you are using some other JMS provider, you need to set one or
both of <literal>queueConnection.queueConnectionFactoryJndiName</literal>
and <literal>topicConnection.topicConnectionFactoryJndiName</literal>
in <literal>seam.properties</literal>, <literal>web.xml</literal>
or <literal>components.xml</literal>.
</para>
<para>
You also need to list topics and queues in <literal>components.xml</literal>
to install Seam managed <literal>TopicPublisher</literal>s and
<literal>QueueSender</literal>s:
</para>
<programlisting role="XML"><![CDATA[<jms:managed-topic-publisher name="stockTickerPublisher"
auto-create="true"
topic-jndi-name="topic/stockTickerTopic"/>
<jms:managed-queue-sender name="paymentQueueSender"
auto-create="true"
queue-jndi-name="queue/paymentQueue"/>]]></programlisting>
</sect2>
<sect2>
<title>Sending messages</title>
<para>
Now, you can inject a JMS <literal>TopicPublisher</literal> and
<literal>TopicSession</literal> into any Seam component to publish
an object to a topic:
</para>
<programlisting role="JAVA"><![CDATA[@Name("stockPriceChangeNotifier")
public class StockPriceChangeNotifier
{
@In private TopicPublisher stockTickerPublisher;
@In private TopicSession topicSession;
public void publish(StockPrice price)
{
try
{
stockTickerPublisher.publish(topicSession.createObjectMessage(price));
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
}]]></programlisting>
<para>or to a queue:</para>
<programlisting role="JAVA"><![CDATA[@Name("paymentDispatcher")
public class PaymentDispatcher
{
@In private QueueSender paymentQueueSender;
@In private QueueSession queueSession;
public void publish(Payment payment)
{
try
{
paymentQueueSender.send(queueSession.createObjectMessage(payment));
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
}]]></programlisting>
</sect2>
<sect2>
<title>Receiving messages using a message-driven bean</title>
<para>
You can process messages using any EJB 3 message-driven bean. The MDB
can even be a Seam component, in which case it's possible to inject
other event- and application- scoped Seam components. Here's an example
of the payment receiver, which delegates to a payment processor.
</para>
<note>
<para>
You'll likely need to set the create attribute on the <literal>@In</literal> annotation to true (i.e.
create = true) to have Seam create an instance of the component being injected. This isn't necessary if
the component supports auto-creation (e.g., it's annotated with <literal>@Autocreate</literal>).
</para>
</note>
<para>
First, create an MDB to receive the message.
</para>
<programlisting role="JAVA"><![CDATA[@MessageDriven(activationConfig = {
@ActivationConfigProperty(
propertyName = "destinationType",
propertyValue = "javax.jms.Queue"
),
@ActivationConfigProperty(
propertyName = "destination",
propertyValue = "queue/paymentQueue"
)
})
@Name("paymentReceiver")
public class PaymentReceiver implements MessageListener
{
@Logger private Log log;
@In(create = true) private PaymentProcessor paymentProcessor;
@Override
public void onMessage(Message message)
{
try
{
paymentProcessor.processPayment((Payment) ((ObjectMessage) message).getObject());
}
catch (JMSException ex)
{
log.error("Message payload did not contain a Payment object", ex);
}
}
}]]></programlisting>
<para>
Then, implement the Seam component to which the receiver delegates processing of the payment.
</para>
<programlisting role="JAVA"><![CDATA[@Name("paymentProcessor")
public class PaymentProcessor
{
@In private EntityManager entityManager;
public void processPayment(Payment payment)
{
// perhaps do something more fancy
entityManager.persist(payment);
}
}]]></programlisting>
<para>
If you are going to be performing transaction operations in your MDB, you should ensure that you
are working with an XA datasource. Otherwise, it won't be possible to rollback database changes
if the database transaction commits and a subsequent operation being performed by the message
fails.
</para>
</sect2>
<sect2>
<title>Receiving messages in the client</title>
<para>
Seam Remoting lets you subscribe to a JMS topic from client-side JavaScript. This is
described in <xref linkend="remoting"/>.
</para>
</sect2>
</sect1>
<sect1>
<title>Asynchronicity</title>
<para>
Asynchronous events and method calls have the same quality of service expectations as
the underlying dispatcher mechanism. The default dispatcher, based upon a
<literal>ScheduledThreadPoolExecutor</literal> performs efficiently but provides no
support for persistent asynchronous tasks, and hence no guarantee that a task
will ever actually be executed. If you're working in an environment that supports
EJB 3.0, and add the following line to <literal>components.xml</literal>:
</para>
<programlisting role="XML"><![CDATA[<async:timer-service-dispatcher/>]]></programlisting>
<para>
then your asynchronous tasks will be processed by the container's EJB timer service. If
you're not familiar with the Timer service, don't worry, you don't need to interact with
it directly if you want to use asynchronous methods in Seam. The important thing to know
is that any good EJB 3.0 implementation will have the option of using persistent timers,
which gives some guarantee that the tasks will eventually be processed.
</para>
<para>
Another alternative is to use the open source Quartz library to manage asynchronous method.
You need to bundle the Quartz library JAR (found in the <literal>lib</literal> directory)
in your EAR and declare it as a Java module in <literal>application.xml</literal>.
The Quartz dispatcher may be configured by adding a Quartz property file to the classpath.
It must be named <literal>seam.quartz.properties</literal>. In addition,
you need to add the following line to <literal>components.xml</literal> to install the
Quartz dispatcher.
</para>
<programlisting role="XML"><![CDATA[<async:quartz-dispatcher/>]]></programlisting>
<para>
The Seam API for the default <literal>ScheduledThreadPoolExecutor</literal>, the EJB3
<literal>Timer</literal>, and the Quartz <literal>Scheduler</literal> are largely the
same. They can just "plug and play" by adding a line to <literal>components.xml</literal>.
</para>
<sect2>
<title>Asynchronous methods</title>
<para>
In simplest form, an asynchronous call just lets a method call be processed asynchronously
(in a different thread) from the caller. We usually use an asynchronous call when we want
to return an immediate response to the client, and let some expensive work be processed in
the background. This pattern works very well in applications which use AJAX, where the
client can automatically poll the server for the result of the work.
</para>
<para>
For EJB components, we annotate the local interface to specify that a method is processed
asynchronously.
</para>
<programlisting role="JAVA"><![CDATA[@Local
public interface PaymentHandler
{
@Asynchronous
public void processPayment(Payment payment);
}]]></programlisting>
<para>
(For JavaBean components we have to annotate the component implementation class.)
</para>
<para>
The use of asynchronicity is transparent to the bean class:
</para>
<programlisting role="JAVA"><![CDATA[@Stateless
@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
public void processPayment(Payment payment)
{
//do some work!
}
}]]></programlisting>
<para>
And also transparent to the client:
</para>
<programlisting role="JAVA"><![CDATA[@Stateful
@Name("paymentAction")
public class CreatePaymentAction
{
@In(create=true) PaymentHandler paymentHandler;
@In Bill bill;
public String pay()
{
paymentHandler.processPayment( new Payment(bill) );
return "success";
}
}]]></programlisting>
<note>
<para>Please distinguish between
<ulink url="http://docs.oracle.com/javaee/6/api/javax/ejb/Asynchronous.html">
EJB 3.1 annotation
<literal>javax.ejb.Asynchronous</literal></ulink> and Seam annotation
<literal>org.jboss.seam.annotations.async.Asynchronous</literal>.
While first is designated for session beans only, the latter works
in non-EJB environment too.
</para>
</note>
<para>
The asynchronous method is processed in a completely new event context and does
not have access to the session or conversation context state of the caller. However,
the business process context <emphasis>is</emphasis> propagated.
</para>
<para>
Asynchronous method calls may be scheduled for later execution using the
<literal>@Duration</literal>, <literal>@Expiration</literal> and
<literal>@IntervalDuration</literal> annotations.
</para>
<programlisting role="JAVA"><![CDATA[@Local
public interface PaymentHandler
{
@Asynchronous
public void processScheduledPayment(Payment payment, @Expiration Date date);
@Asynchronous
public void processRecurringPayment(Payment payment,
@Expiration Date date,
@IntervalDuration Long interval)'
}]]></programlisting>
<programlisting role="JAVA"><![CDATA[@Stateful
@Name("paymentAction")
public class CreatePaymentAction
{
@In(create=true) PaymentHandler paymentHandler;
@In Bill bill;
public String schedulePayment()
{
paymentHandler.processScheduledPayment( new Payment(bill), bill.getDueDate() );
return "success";
}
public String scheduleRecurringPayment()
{
paymentHandler.processRecurringPayment( new Payment(bill), bill.getDueDate(),
ONE_MONTH );
return "success";
}
}]]></programlisting>
<para>
Both client and server may access the <literal>Timer</literal> object associated with
the invocation. The <literal>Timer</literal> object shown below is the EJB3 timer when you use the EJB3 dispatcher. For the default <literal>ScheduledThreadPoolExecutor</literal>, the returned object is <literal>Future</literal> from the JDK. For the Quartz dispatcher, it returns <literal>QuartzTriggerHandle</literal>, which we will discuss in the next section.
</para>
<programlisting role="JAVA"><![CDATA[@Local
public interface PaymentHandler
{
@Asynchronous
public Timer processScheduledPayment(Payment payment, @Expiration Date date);
}]]></programlisting>
<programlisting role="JAVA"><![CDATA[@Stateless
@Name("paymentHandler")
public class PaymentHandlerBean implements PaymentHandler
{
@In Timer timer;
public Timer processScheduledPayment(Payment payment, @Expiration Date date)
{
//do some work!
return timer; //note that return value is completely ignored
}
}]]></programlisting>
<programlisting role="JAVA"><![CDATA[@Stateful
@Name("paymentAction")
public class CreatePaymentAction
{
@In(create=true) PaymentHandler paymentHandler;
@In Bill bill;
public String schedulePayment()
{
Timer timer = paymentHandler.processScheduledPayment( new Payment(bill),
bill.getDueDate() );
return "success";
}
}]]></programlisting>
<para>
Asynchronous methods cannot return any other value to the caller.
</para>
</sect2>
<sect2>
<title>Asynchronous methods with the Quartz Dispatcher</title>
<para>
The Quartz dispatcher (see earlier on how to install it) allows you to use the <literal>@Asynchronous</literal>, <literal>@Duration</literal>, <literal>@Expiration</literal>, and <literal>@IntervalDuration</literal> annotations as above. But it has some powerful additional features. The Quartz dispatcher supports three new annotations.
</para>
<para>The <literal>@FinalExpiration</literal> annotation specifies an end date for the recurring task. Note that you can inject the <code>QuartzTriggerHandle</code>.</para>
<programlisting role="JAVA"><![CDATA[
@In QuartzTriggerHandle timer;
// Defines the method in the "processor" component
@Asynchronous
public QuartzTriggerHandle schedulePayment(@Expiration Date when,
@IntervalDuration Long interval,
@FinalExpiration Date endDate,
Payment payment)
{
// do the repeating or long running task until endDate
}
... ...
// Schedule the task in the business logic processing code
// Starts now, repeats every hour, and ends on May 10th, 2010
Calendar cal = Calendar.getInstance ();
cal.set (2010, Calendar.MAY, 10);
processor.schedulePayment(new Date(), 60*60*1000, cal.getTime(), payment);
]]></programlisting>
<para>Note that the method returns the <literal>QuartzTriggerHandle</literal> object, which you can use later to stop, pause, and resume the scheduler. The <literal>QuartzTriggerHandle</literal> object is serializable, so you can save it into the database if you need to keep it around for extended period of time.</para>
<programlisting role="JAVA"><![CDATA[QuartzTriggerHandle handle =
processor.schedulePayment(payment.getPaymentDate(),
payment.getPaymentCron(),
payment);
payment.setQuartzTriggerHandle( handle );
// Save payment to DB
// later ...
// Retrieve payment from DB
// Cancel the remaining scheduled tasks
payment.getQuartzTriggerHandle().cancel();
]]></programlisting>
<para>The <literal>@IntervalCron</literal> annotation supports Unix cron job syntax for task scheduling. For instance, the following asynchronous method runs at 2:10pm and at 2:44pm every Wednesday in the month of March.
</para>
<programlisting role="JAVA"><![CDATA[
// Define the method
@Asynchronous
public QuartzTriggerHandle schedulePayment(@Expiration Date when,
@IntervalCron String cron,
Payment payment)
{
// do the repeating or long running task
}
... ...
// Schedule the task in the business logic processing code
QuartzTriggerHandle handle =
processor.schedulePayment(new Date(), "0 10,44 14 ? 3 WED", payment);
]]></programlisting>
<para>The <literal>@IntervalBusinessDay</literal> annotation supports invocation on the "nth Business Day" scenario. For instance, the following asynchronous method runs at 14:00 on the 2nd business day of each month. By default, it excludes all weekends and US federal holidays until 2010 from the business days.
</para>
<programlisting role="JAVA"><![CDATA[
// Define the method
@Asynchronous
public QuartzTriggerHandle schedulePayment(@Expiration Date when,
@IntervalBusinessDay NthBusinessDay nth,
Payment payment)
{
// do the repeating or long running task
}
... ...
// Schedule the task in the business logic processing code
QuartzTriggerHandle handle =
processor.schedulePayment(new Date(),
new NthBusinessDay(2, "14:00", WEEKLY), payment);
]]></programlisting>
<para>The <literal>NthBusinessDay</literal> object contains the configuration of the invocation trigger. You can specify more holidays (e.g., company holidays, non-US holidays etc.) via the <literal>additionalHolidays</literal> property.</para>
<programlisting role="JAVA"><![CDATA[
public class NthBusinessDay implements Serializable
{
int n;
String fireAtTime;
List <Date> additionalHolidays;
BusinessDayIntervalType interval;
boolean excludeWeekends;
boolean excludeUsFederalHolidays;
public enum BusinessDayIntervalType { WEEKLY, MONTHLY, YEARLY }
public NthBusinessDay ()
{
n = 1;
fireAtTime = "12:00";
additionalHolidays = new ArrayList <Date> ();
interval = BusinessDayIntervalType.WEEKLY;
excludeWeekends = true;
excludeUsFederalHolidays = true;
}
... ...
}
]]></programlisting>
<para>The <literal>@IntervalDuration</literal>, <literal>@IntervalCron</literal>, and <literal>@IntervalNthBusinessDay</literal> annotations are mutually exclusive. If they are used in the same method, a <literal>RuntimeException</literal> will be thrown.</para>
</sect2>
<sect2>
<title>Asynchronous events</title>
<para>
Component-driven events may also be asynchronous. To raise an event for asynchronous
processing, simply call the <literal>raiseAsynchronousEvent()</literal> method of
the <literal>Events</literal> class. To schedule a timed event, call the
<literal>raiseTimedEvent()</literal> method, passing a <emphasis>schedule</emphasis>
object (for the default dispatcher or timer service dispatcher, use <literal>TimerSchedule</literal>).
Components may observe asynchronous events in the usual way, but remember that only the
business process context is propagated to the asynchronous thread.
</para>
</sect2>
<sect2>
<title>Handling exceptions from asynchronous calls</title>
<para>
Each asynchronous dispatcher behaves differently when an
exception propagates through it. For example, the
<literal>java.util.concurrent</literal> dispatcher will suspend
further executions of a call which repeats, and the EJB3 timer
service will swallow the exception. Seam therefore catches any
exception which propagates out of the asynchronous call before
it reaches the dispatcher.
</para>
<para>
By default, any exception which propagates out from an
asynchronous execution will be caught and logged at error level.
You can customize this behavior globally by overriding the
<literal>org.jboss.seam.async.asynchronousExceptionHandler</literal>
component:
</para>
<programlisting role="JAVA"><![CDATA[@Scope(ScopeType.STATELESS)
@Name("org.jboss.seam.async.asynchronousExceptionHandler")
public class MyAsynchronousExceptionHandler extends AsynchronousExceptionHandler {
@Logger Log log;
@In Future timer;
@Override
public void handleException(Exception exception) {
log.debug(exception);
timer.cancel(false);
}
}]]></programlisting>
<para>
Here, for example, using <literal>java.util.concurrent</literal>
dispatcher, we inject its control object and cancel all future
invocations when an exception is encountered
</para>
<para>
You can also alter this behavior for an individual component by
implementing the method
<literal>public void handleAsynchronousException(Exception exception);</literal>
on the component. For example:
</para>
<programlisting role="JAVA"><![CDATA[ public void handleAsynchronousException(Exception exception) {
log.fatal(exception);
}]]></programlisting>
</sect2>
</sect1>
</chapter>