|
45 | 45 | <para>
|
46 | 46 | JMS messaging with Spring Python revolves around the idea of using
|
47 | 47 | a connection factory for obtaining a connection to a JMS provider and
|
48 |
| - <classname>springpython.jms.core.JmsTemplate</classname> as a means for |
| 48 | + <classname> |
| 49 | + <link linkend="jms-jms-template">springpython.jms.core.JmsTemplate</link> |
| 50 | + </classname> as a means for |
49 | 51 | sending and receiving messages. A JmsTemplate instance is tied
|
50 | 52 | to a connection factory however a single connection factory may be safely
|
51 | 53 | reused across multiple JmsTemplates.
|
52 | 54 | </para>
|
| 55 | + <para> |
| 56 | + In addition to that, |
| 57 | + <classname> |
| 58 | + <link linkend="jms-simple-message-listener-container">springpython.jms.listener.SimpleMessageListenerContainer</link> |
| 59 | + </classname> |
| 60 | + allows for a purely configuration-driven way to set up background JMS |
| 61 | + listeners to receive messages from JMS providers. |
| 62 | + </para> |
53 | 63 | </section>
|
54 | 64 |
|
55 | 65 | <section id="jms-dependencies">
|
56 | 66 | <title>Dependencies</title>
|
57 | 67 | <para>
|
58 | 68 | Support for JMS messaging with WebSphere MQ is built on top of
|
59 |
| - the CPython-only <ulink url="http://pymqi.sf.net">PyMQI</ulink> library which provides |
| 69 | + the CPython-only <ulink url="https://launchpad.net/pymqi">PyMQI</ulink> library which provides |
60 | 70 | Python applications an access to WebSphere MQ queue managers. You need to separately install
|
61 | 71 | PyMQI in order to use <classname>springpython.jms.factory.WebSphereMQConnectionFactory</classname>.
|
62 | 72 | PyMQI, in turn, needs a <emphasis>WebSphere MQ client</emphasis>, a runtime library
|
63 | 73 | which may be freely downloaded from IBM's site.
|
64 | 74 | </para>
|
| 75 | + <para> |
| 76 | + <classname>SimpleMessageListenerContainer</classname>, a Spring Python component which |
| 77 | + helps with running background JMS listeners, requires the installation |
| 78 | + of <ulink url="http://code.google.com/p/circuits/">Circuits 1.2+</ulink> |
| 79 | + and <ulink url="http://pypi.python.org/pypi/threadpool">threadpool 1.2.7 or newer.</ulink> |
| 80 | + </para> |
65 | 81 | </section>
|
66 | 82 |
|
67 | 83 | <section id="jms-quick-start">
|
@@ -112,7 +128,7 @@ objects:
|
112 | 128 | queue_manager: QM.1
|
113 | 129 | channel: SVRCONN.1
|
114 | 130 | host: 192.168.1.121
|
115 |
| - listener_port: 1434 |
| 131 | + listener_port: "1434" |
116 | 132 |
|
117 | 133 | - object: MyTemplate
|
118 | 134 | class: springpython.jms.core.JmsTemplate
|
@@ -197,7 +213,7 @@ objects:
|
197 | 213 | queue_manager: QM.1
|
198 | 214 | channel: SVRCONN.1
|
199 | 215 | host: 192.168.1.121
|
200 |
| - listener_port: 1434 |
| 216 | + listener_port: "1434" |
201 | 217 |
|
202 | 218 | - object: MyTemplate
|
203 | 219 | class: springpython.jms.core.JmsTemplate
|
@@ -230,6 +246,54 @@ jms_template.receive(queue1)
|
230 | 246 | # care of shutting down the factory. No need for manually destroying it.
|
231 | 247 | ]]></programlisting>
|
232 | 248 |
|
| 249 | + <para> |
| 250 | + Here's a sample YAML context utilizing the SimpleMessageListenerContainer |
| 251 | + component and an accompanying Python code using it. As you can see, |
| 252 | + a mere fact of providing the configuration allows for receiving |
| 253 | + the messages. |
| 254 | + </para> |
| 255 | +<programlisting><![CDATA[ |
| 256 | +objects: |
| 257 | + - object: connection_factory |
| 258 | + class: springpython.jms.factory.WebSphereMQConnectionFactory |
| 259 | + properties: |
| 260 | + queue_manager: QM.1 |
| 261 | + channel: SVRCONN.1 |
| 262 | + host: 192.168.1.121 |
| 263 | + listener_port: "1434" |
| 264 | + |
| 265 | + - object: message_handler |
| 266 | + class: app.MyMessageHandler |
| 267 | + |
| 268 | + - object: listener_container |
| 269 | + class: springpython.jms.listener.SimpleMessageListenerContainer |
| 270 | + properties: |
| 271 | + factory: {ref: connection_factory} |
| 272 | + handler: {ref: message_handler} |
| 273 | + destination: TEST.1 |
| 274 | +]]></programlisting> |
| 275 | + |
| 276 | +<programlisting><![CDATA[ |
| 277 | +
|
| 278 | +# app.py |
| 279 | +
|
| 280 | +from springpython.config import YamlConfig |
| 281 | +from springpython.context import ApplicationContext |
| 282 | +
|
| 283 | +class MyMessageHandler(object): |
| 284 | + def handle(self, message): |
| 285 | + print "Got message!", message |
| 286 | +
|
| 287 | +if __name__ == "__main__": |
| 288 | +
|
| 289 | + # Obtaining a context will automatically start the SimpleMessageListenerContainer and its listeners in background. |
| 290 | + container = ApplicationContext(YamlConfig("./context.yml")) |
| 291 | +
|
| 292 | + while True: |
| 293 | + # Here goes the application's logic. Any JMS messages, as configured |
| 294 | + # in ./context.yml, will be passed in to a singleton MyMessageHandler instance. |
| 295 | + pass |
| 296 | +]]></programlisting> |
233 | 297 | </section>
|
234 | 298 | </section>
|
235 | 299 |
|
@@ -444,7 +508,7 @@ objects:
|
444 | 508 | queue_manager: QM.1
|
445 | 509 | channel: SVRCONN.1
|
446 | 510 | host: 192.168.1.121
|
447 |
| - listener_port: 1434 |
| 511 | + listener_port: "1434" |
448 | 512 | ]]></programlisting>
|
449 | 513 |
|
450 | 514 | <para>
|
@@ -483,7 +547,8 @@ objects:
|
483 | 547 | <title>springpython.jms.core.JmsTemplate</title>
|
484 | 548 |
|
485 | 549 | <para>
|
486 |
| - JmsTemplate is the class to use for sending and receiving JMS messages. |
| 550 | + JmsTemplate is the class to use for sending JMS messages; along |
| 551 | + with SimpleMessageListenerContainer it may also be used in order to receive them. |
487 | 552 | A template must be associated with a connection factory and once
|
488 | 553 | configured, may be used for communicating in both directions. It's
|
489 | 554 | up to you to decide whether in your circumstances it makes sense
|
@@ -533,7 +598,7 @@ objects:
|
533 | 598 | queue_manager: QM.1
|
534 | 599 | channel: SVRCONN.1
|
535 | 600 | host: 192.168.1.121
|
536 |
| - listener_port: 1434 |
| 601 | + listener_port: "1434" |
537 | 602 |
|
538 | 603 | - object: jms_template
|
539 | 604 | class: springpython.jms.core.JmsTemplate
|
@@ -876,6 +941,12 @@ print jms_template.receive(queue2, 2000)
|
876 | 941 | # We're not using an IoC so we need to shut down the connection factory ourselves.
|
877 | 942 | factory.destroy()
|
878 | 943 | ]]></programlisting>
|
| 944 | + |
| 945 | + <para> |
| 946 | + Note that <link linkend="jms-simple-message-listener-container">SimpleMessageListenerContainer</link> |
| 947 | + provides a complementary way for receiving the messages, particularly |
| 948 | + well suited for long-running processes, such as servers. |
| 949 | + </para> |
879 | 950 |
|
880 | 951 | </section>
|
881 | 952 |
|
@@ -1090,6 +1161,170 @@ factory.destroy()
|
1090 | 1161 | </section>
|
1091 | 1162 | </section>
|
1092 | 1163 |
|
| 1164 | + <section id="jms-simple-message-listener-container"> |
| 1165 | + <title>springpython.jms.listener.SimpleMessageListenerContainer and background JMS listeners</title> |
| 1166 | + |
| 1167 | + <para> |
| 1168 | + SimpleMessageListenerContainer is a configuration-driven component |
| 1169 | + which is used to receive messages from JMS destinations. Once configured, |
| 1170 | + the container starts as many background listeners as requested and |
| 1171 | + each listener gets assigned a pool of threads to handle the incoming |
| 1172 | + requests. The number of listeners started and threads in a pool is |
| 1173 | + fixed upon the configuration is read and the container is started, |
| 1174 | + they cannot be dynamically altered in runtime. |
| 1175 | + </para> |
| 1176 | + <para> |
| 1177 | + The advantage of using SimpleMessageListenerContainer comes from |
| 1178 | + the fact that all you need to do in order to receive the messages |
| 1179 | + is to create your own handler class and to configure the container, |
| 1180 | + no JMS coding is required so you're focusing on creating the business |
| 1181 | + logic, not on the JMS boilerplate. |
| 1182 | + </para> |
| 1183 | + |
| 1184 | + <para> |
| 1185 | + <table id="jms-simple-message-listener-container-properties"> |
| 1186 | + |
| 1187 | + <title><classname>SimpleMessageListenerContainer</classname> properties</title> |
| 1188 | + |
| 1189 | + <tgroup cols="2"> |
| 1190 | + <tbody> |
| 1191 | + <row> |
| 1192 | + <entry>factory</entry> |
| 1193 | + <entry>A reference to a JMS connection factory; |
| 1194 | + defaults to None and must be set manually. |
| 1195 | + </entry> |
| 1196 | + </row> |
| 1197 | + <row> |
| 1198 | + <entry>destination</entry> |
| 1199 | + <entry>Name of a JMS destination to read the messages |
| 1200 | + off. Defaults to None and must be set manually. |
| 1201 | + </entry> |
| 1202 | + </row> |
| 1203 | + <row> |
| 1204 | + <entry>handler</entry> |
| 1205 | + <entry>A reference to an object which will be receiving |
| 1206 | + messages read from the JMS destination. A handler must |
| 1207 | + implement <emphasis>handle(self, message)</emphasis> |
| 1208 | + method, of which the <emphasis>message</emphasis> argument |
| 1209 | + is a <link linkend="jms-text-message">TextMessage</link> |
| 1210 | + instance. There is a convenience class, |
| 1211 | + <classname>springpython.jms.listener.MessageHandler</classname>, |
| 1212 | + which exposes such a method. The exact number of handlers |
| 1213 | + available for message processing is controlled via |
| 1214 | + the <emphasis>handlers_per_listener</emphasis> |
| 1215 | + property. The <emphasis>handler</emphasis> parameter |
| 1216 | + defaults to None and must be set manually. |
| 1217 | + </entry> |
| 1218 | + </row> |
| 1219 | + <row> |
| 1220 | + <entry>concurrent_listeners</entry> |
| 1221 | + <entry>Sets a number of background processes that |
| 1222 | + connect to a JMS provider and read messages off |
| 1223 | + the destination. Default value is 1. |
| 1224 | + </entry> |
| 1225 | + </row> |
| 1226 | + <row> |
| 1227 | + <entry>handlers_per_listener</entry> |
| 1228 | + <entry>Each concurrent listener is assigned a thread pool |
| 1229 | + of a fixed size, given by the <emphasis>handlers_per_listener</emphasis> |
| 1230 | + parameter. Upon receiving a message, it will be dispatched |
| 1231 | + to a thread which will in turn invoke the message |
| 1232 | + handler's <emphasis>handle</emphasis> method. |
| 1233 | + The pool's size defaults to 2. |
| 1234 | + </entry> |
| 1235 | + </row> |
| 1236 | + <row> |
| 1237 | + <entry>wait_interval</entry> |
| 1238 | + <entry>A value in milliseconds expressing how often |
| 1239 | + each of the listeners will check for the arrival |
| 1240 | + of a new message. Defaults to 1000 (1 second). |
| 1241 | + </entry> |
| 1242 | + </row> |
| 1243 | + </tbody> |
| 1244 | + </tgroup> |
| 1245 | + </table> |
| 1246 | + </para> |
| 1247 | + |
| 1248 | + <para> |
| 1249 | + Here's an example showing SimpleMessageListenerContainer in action |
| 1250 | + together with YamlConfig's abstract objects definitions. |
| 1251 | + customer_queue, credit_account_queue and deposit_account_queue |
| 1252 | + subclass the listener_container object which holds the information |
| 1253 | + common to all definitions of JMS destinations. 4 listeners will |
| 1254 | + be assigned to each of the JMS destination, every listener will be |
| 1255 | + assigned a pool of 5 threads for handling the messages read; |
| 1256 | + a wait interval of 700 milliseconds has been set. |
| 1257 | + |
| 1258 | +<programlisting><![CDATA[ |
| 1259 | +objects: |
| 1260 | + - object: connection_factory |
| 1261 | + class: springpython.jms.factory.WebSphereMQConnectionFactory |
| 1262 | + properties: |
| 1263 | + queue_manager: QM.1 |
| 1264 | + channel: SVRCONN.1 |
| 1265 | + host: 192.168.1.121 |
| 1266 | + listener_port: "1434" |
| 1267 | + |
| 1268 | + - object: message_handler |
| 1269 | + class: app.MyMessageHandler |
| 1270 | + |
| 1271 | + - object: listener_container |
| 1272 | + abstract: True |
| 1273 | + class: springpython.jms.listener.SimpleMessageListenerContainer |
| 1274 | + concurrent_listeners: "4" |
| 1275 | + handlers_per_listener: "5" |
| 1276 | + wait_interval: "700" |
| 1277 | + properties: |
| 1278 | + factory: {ref: connection_factory} |
| 1279 | + handler: {ref: message_handler} |
| 1280 | + |
| 1281 | + - object: customer_queue |
| 1282 | + parent: listener_container |
| 1283 | + properties: |
| 1284 | + destination: CUST.QUEUE.1 |
| 1285 | + |
| 1286 | + - object: credit_account_queue |
| 1287 | + parent: listener_container |
| 1288 | + properties: |
| 1289 | + destination: CREDACCT.QUEUE.1 |
| 1290 | + |
| 1291 | + - object: deposit_account_queue |
| 1292 | + parent: listener_container |
| 1293 | + properties: |
| 1294 | + destination: DEPACCT.QUEUE.1 |
| 1295 | +]]></programlisting> |
| 1296 | + |
| 1297 | + Here's a Python code using the above IoC configuration. Note that the fact |
| 1298 | + of reading a configuration alone suffices for JMS listeners to be started and |
| 1299 | + run in the background of the main application. |
| 1300 | + |
| 1301 | +<programlisting><![CDATA[ |
| 1302 | +# app.py |
| 1303 | +
|
| 1304 | +from springpython.config import YamlConfig |
| 1305 | +from springpython.context import ApplicationContext |
| 1306 | +
|
| 1307 | +class MyMessageHandler(object): |
| 1308 | + def handle(self, message): |
| 1309 | + print "Got message!", message |
| 1310 | +
|
| 1311 | +if __name__ == "__main__": |
| 1312 | +
|
| 1313 | + # Obtaining a context will automatically start the SimpleMessageListenerContainer |
| 1314 | + # and its listeners in background. |
| 1315 | + container = ApplicationContext(YamlConfig("./context.yml")) |
| 1316 | +
|
| 1317 | + while True: |
| 1318 | + # Here goes the main application's logic, which does nothing in this case. |
| 1319 | + # However, the listeners have been already started and incoming messages |
| 1320 | + # will be passed in to MyMessageHandler instance (as configured in YamlConfig). |
| 1321 | + pass |
| 1322 | +]]></programlisting> |
| 1323 | + |
| 1324 | + </para> |
| 1325 | + |
| 1326 | + </section> |
| 1327 | + |
1093 | 1328 | <section id="jms-text-message">
|
1094 | 1329 | <title>springpython.jms.core.TextMessage</title>
|
1095 | 1330 |
|
@@ -1530,10 +1765,15 @@ Hello!
|
1530 | 1765 | </para>
|
1531 | 1766 |
|
1532 | 1767 | <para>
|
1533 |
| - The only JMS logger currently employed by Spring Python is |
1534 |
| - <classname>springpython.jms.factory.WebSphereMQConnectionFactory</classname> |
1535 |
| - and here's how it can be configured to work at the |
1536 |
| - <classname>INFO</classname> level: |
| 1768 | + JMS loggers currently employed by Spring Python are |
| 1769 | + <classname>springpython.jms.factory.WebSphereMQConnectionFactory</classname>, |
| 1770 | + <classname>springpython.jms.listener.SimpleMessageListenerContainer</classname> and |
| 1771 | + <classname>springpython.jms.listener.WebSphereMQListener(<emphasis>LISTENER_INSTANCE_ID</emphasis>)</classname>. |
| 1772 | + </para> |
| 1773 | + |
| 1774 | + <para> |
| 1775 | + Here's how the WebSphere MQ connection factory's logger can be |
| 1776 | + configured to work at the <classname>INFO</classname> level: |
1537 | 1777 | </para>
|
1538 | 1778 |
|
1539 | 1779 | <programlisting><![CDATA[
|
@@ -1578,6 +1818,24 @@ jms_logger.setLevel(level=TRACE1)
|
1578 | 1818 | jms_logger.addHandler(handler)
|
1579 | 1819 | ]]></programlisting>
|
1580 | 1820 |
|
| 1821 | + <para> |
| 1822 | + <classname>springpython.jms.listener.SimpleMessageListenerContainer</classname> |
| 1823 | + is the logger used by the JMS listener container itself. |
| 1824 | + </para> |
| 1825 | + |
| 1826 | + <para> |
| 1827 | + Each WebSphere MQ listener is assigned a |
| 1828 | + <classname>springpython.jms.listener.WebSphereMQListener(<emphasis>LISTENER_INSTANCE_ID</emphasis>)</classname> |
| 1829 | + logger, where <emphasis>LISTENER_INSTANCE_ID</emphasis> is an identifier |
| 1830 | + uniquely associated with a listener to form a full name of a logger, |
| 1831 | + such as |
| 1832 | + <classname>springpython.jms.listener.WebSphereMQListener(0xc7f5e0)</classname>. |
| 1833 | + To be precise, its value is obtained by invoking hex(id(self)) on the listener's |
| 1834 | + instance. Note that the value is not guaranteed to be globally unique, |
| 1835 | + it's just an identifier of the Python object so its value may be |
| 1836 | + very well reused across application's restarts. |
| 1837 | + </para> |
| 1838 | + |
1581 | 1839 | <para>
|
1582 | 1840 | How much information is being logged depends on the logging level, the
|
1583 | 1841 | average message size, the messages'
|
|
0 commit comments