stomper 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/.gitignore +5 -0
  2. data/{spec/spec.opts → .rspec} +0 -2
  3. data/Gemfile +4 -0
  4. data/LICENSE +201 -201
  5. data/README.md +130 -0
  6. data/Rakefile +5 -0
  7. data/examples/basic.rb +38 -0
  8. data/examples/events.rb +54 -0
  9. data/features/acking_messages.feature +147 -0
  10. data/features/disconnecting.feature +12 -0
  11. data/features/establish_connection.feature +44 -0
  12. data/features/protocol_version_negotiation.feature +61 -0
  13. data/features/receipts.feature +72 -0
  14. data/features/scopes.feature +32 -0
  15. data/features/secure_connections.feature +38 -0
  16. data/features/send_and_message.feature +28 -0
  17. data/features/steps/acking_messages_steps.rb +39 -0
  18. data/features/steps/disconnecting_steps.rb +8 -0
  19. data/features/steps/establish_connection_steps.rb +74 -0
  20. data/features/steps/frame_transmission_steps.rb +35 -0
  21. data/features/steps/protocol_version_negotiation_steps.rb +15 -0
  22. data/features/steps/receipts_steps.rb +79 -0
  23. data/features/steps/scopes_steps.rb +52 -0
  24. data/features/steps/secure_connections_steps.rb +41 -0
  25. data/features/steps/send_and_message_steps.rb +35 -0
  26. data/features/steps/subscribing_steps.rb +36 -0
  27. data/features/steps/threaded_receiver_steps.rb +8 -0
  28. data/features/steps/transactions_steps.rb +0 -0
  29. data/features/subscribing.feature +151 -0
  30. data/features/support/env.rb +11 -0
  31. data/features/support/header_helpers.rb +12 -0
  32. data/features/support/ssl/README +6 -0
  33. data/features/support/ssl/broker_cert.csr +17 -0
  34. data/features/support/ssl/broker_cert.pem +72 -0
  35. data/features/support/ssl/broker_key.pem +27 -0
  36. data/features/support/ssl/client_cert.csr +17 -0
  37. data/features/support/ssl/client_cert.pem +72 -0
  38. data/features/support/ssl/client_key.pem +27 -0
  39. data/features/support/ssl/demoCA/cacert.pem +17 -0
  40. data/features/support/ssl/demoCA/index.txt +2 -0
  41. data/features/support/ssl/demoCA/index.txt.attr +1 -0
  42. data/features/support/ssl/demoCA/index.txt.attr.old +1 -0
  43. data/features/support/ssl/demoCA/index.txt.old +1 -0
  44. data/features/support/ssl/demoCA/newcerts/01.pem +72 -0
  45. data/features/support/ssl/demoCA/newcerts/02.pem +72 -0
  46. data/features/support/ssl/demoCA/private/cakey.pem +17 -0
  47. data/features/support/ssl/demoCA/serial +1 -0
  48. data/features/support/ssl/demoCA/serial.old +1 -0
  49. data/features/support/test_stomp_server.rb +150 -0
  50. data/features/threaded_receiver.feature +11 -0
  51. data/features/transactions.feature +66 -0
  52. data/lib/stomper.rb +30 -20
  53. data/lib/stomper/connection.rb +442 -102
  54. data/lib/stomper/errors.rb +59 -0
  55. data/lib/stomper/extensions.rb +10 -0
  56. data/lib/stomper/extensions/common.rb +258 -0
  57. data/lib/stomper/extensions/events.rb +213 -0
  58. data/lib/stomper/extensions/heartbeat.rb +101 -0
  59. data/lib/stomper/extensions/scoping.rb +56 -0
  60. data/lib/stomper/frame.rb +54 -0
  61. data/lib/stomper/frame_serializer.rb +217 -0
  62. data/lib/stomper/headers.rb +15 -0
  63. data/lib/stomper/receipt_manager.rb +36 -0
  64. data/lib/stomper/receivers.rb +7 -0
  65. data/lib/stomper/receivers/threaded.rb +71 -0
  66. data/lib/stomper/scopes.rb +9 -0
  67. data/lib/stomper/scopes/header_scope.rb +49 -0
  68. data/lib/stomper/scopes/receipt_scope.rb +44 -0
  69. data/lib/stomper/scopes/transaction_scope.rb +109 -0
  70. data/lib/stomper/sockets.rb +66 -28
  71. data/lib/stomper/subscription_manager.rb +79 -0
  72. data/lib/stomper/support.rb +68 -0
  73. data/lib/stomper/support/1.8/frame_serializer.rb +53 -0
  74. data/lib/stomper/support/1.8/headers.rb +183 -0
  75. data/lib/stomper/support/1.9/frame_serializer.rb +64 -0
  76. data/lib/stomper/support/1.9/headers.rb +172 -0
  77. data/lib/stomper/support/ruby.rb +13 -0
  78. data/lib/stomper/uris.rb +49 -0
  79. data/lib/stomper/version.rb +7 -0
  80. data/spec/spec_helper.rb +13 -9
  81. data/spec/stomper/connection_spec.rb +712 -0
  82. data/spec/stomper/extensions/common_spec.rb +187 -0
  83. data/spec/stomper/extensions/events_spec.rb +78 -0
  84. data/spec/stomper/extensions/heartbeat_spec.rb +103 -0
  85. data/spec/stomper/extensions/scoping_spec.rb +21 -0
  86. data/spec/stomper/frame_serializer_1.8_spec.rb +318 -0
  87. data/spec/stomper/frame_serializer_spec.rb +316 -0
  88. data/spec/stomper/frame_spec.rb +36 -0
  89. data/spec/stomper/headers_spec.rb +224 -0
  90. data/spec/stomper/receipt_manager_spec.rb +91 -0
  91. data/spec/stomper/receivers/threaded_spec.rb +116 -0
  92. data/spec/stomper/scopes/header_scope_spec.rb +42 -0
  93. data/spec/stomper/scopes/receipt_scope_spec.rb +51 -0
  94. data/spec/stomper/scopes/transaction_scope_spec.rb +183 -0
  95. data/spec/stomper/sockets_spec.rb +113 -0
  96. data/spec/stomper/subscription_manager_spec.rb +107 -0
  97. data/spec/stomper/support_spec.rb +69 -0
  98. data/spec/stomper/uris_spec.rb +54 -0
  99. data/spec/stomper_spec.rb +9 -0
  100. data/spec/support/custom_argument_matchers.rb +57 -0
  101. data/spec/support/existential_frame_matchers.rb +19 -0
  102. data/spec/support/frame_header_matchers.rb +10 -0
  103. data/stomper.gemspec +30 -0
  104. metadata +272 -97
  105. data/AUTHORS +0 -21
  106. data/CHANGELOG +0 -20
  107. data/README.rdoc +0 -120
  108. data/lib/stomper/client.rb +0 -34
  109. data/lib/stomper/frame_reader.rb +0 -73
  110. data/lib/stomper/frame_writer.rb +0 -21
  111. data/lib/stomper/frames.rb +0 -39
  112. data/lib/stomper/frames/abort.rb +0 -10
  113. data/lib/stomper/frames/ack.rb +0 -25
  114. data/lib/stomper/frames/begin.rb +0 -11
  115. data/lib/stomper/frames/client_frame.rb +0 -89
  116. data/lib/stomper/frames/commit.rb +0 -10
  117. data/lib/stomper/frames/connect.rb +0 -10
  118. data/lib/stomper/frames/connected.rb +0 -30
  119. data/lib/stomper/frames/disconnect.rb +0 -10
  120. data/lib/stomper/frames/error.rb +0 -21
  121. data/lib/stomper/frames/message.rb +0 -48
  122. data/lib/stomper/frames/receipt.rb +0 -19
  123. data/lib/stomper/frames/send.rb +0 -10
  124. data/lib/stomper/frames/server_frame.rb +0 -38
  125. data/lib/stomper/frames/subscribe.rb +0 -42
  126. data/lib/stomper/frames/unsubscribe.rb +0 -19
  127. data/lib/stomper/open_uri_interface.rb +0 -41
  128. data/lib/stomper/receipt_handlers.rb +0 -23
  129. data/lib/stomper/receiptor.rb +0 -38
  130. data/lib/stomper/subscriber.rb +0 -76
  131. data/lib/stomper/subscription.rb +0 -128
  132. data/lib/stomper/subscriptions.rb +0 -95
  133. data/lib/stomper/threaded_receiver.rb +0 -59
  134. data/lib/stomper/transaction.rb +0 -185
  135. data/lib/stomper/transactor.rb +0 -50
  136. data/lib/stomper/uri.rb +0 -55
  137. data/spec/client_spec.rb +0 -29
  138. data/spec/connection_spec.rb +0 -22
  139. data/spec/frame_reader_spec.rb +0 -37
  140. data/spec/frame_writer_spec.rb +0 -27
  141. data/spec/frames/client_frame_spec.rb +0 -66
  142. data/spec/frames/indirect_frame_spec.rb +0 -45
  143. data/spec/frames/server_frame_spec.rb +0 -85
  144. data/spec/open_uri_interface_spec.rb +0 -132
  145. data/spec/receiptor_spec.rb +0 -35
  146. data/spec/shared_connection_examples.rb +0 -79
  147. data/spec/subscriber_spec.rb +0 -77
  148. data/spec/subscription_spec.rb +0 -157
  149. data/spec/subscriptions_spec.rb +0 -145
  150. data/spec/threaded_receiver_spec.rb +0 -33
  151. data/spec/transaction_spec.rb +0 -139
  152. data/spec/transactor_spec.rb +0 -46
@@ -0,0 +1,59 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Namespace for exceptions associated with this gem.
4
+ module Stomper::Errors
5
+ # A common base class for errors raised by the Stomper gem
6
+ # @abstract
7
+ class StomperError < StandardError; end
8
+
9
+ # Low level error raised when the broker transmits data that violates
10
+ # the Stomp protocol specification.
11
+ # @abstract
12
+ class FatalProtocolError < StomperError; end
13
+
14
+ # Raised when an invalid character is encountered in a header
15
+ class InvalidHeaderCharacterError < FatalProtocolError; end
16
+
17
+ # Raised when an invalid escape sequence is encountered in a header name or value
18
+ class InvalidHeaderEscapeSequenceError < FatalProtocolError; end
19
+
20
+ # Raised when a malformed header is encountered. For example, if a header
21
+ # line does not contain ':'
22
+ class MalformedHeaderError < FatalProtocolError; end
23
+
24
+ # Raised when a malformed frame is encountered on the stream. For example,
25
+ # if a frame is not properly terminated with the {Stomper::FrameIO::FRAME_TERMINATOR}
26
+ # character.
27
+ class MalformedFrameError < FatalProtocolError; end
28
+
29
+ # An error that is raised as a result of a misconfiguration of the client
30
+ # connection
31
+ # @abstract
32
+ class FatalConnectionError < StomperError; end
33
+
34
+ # Raised when a connection has been configured with an unsupported protocol
35
+ # version. This can be due to end user misconfiguration, or due to improper
36
+ # protocol negotiation with the message broker.
37
+ class UnsupportedProtocolVersionError < FatalConnectionError; end
38
+
39
+ # Raised when an attempt to connect to the broker results in an unexpected
40
+ # exchange.
41
+ class ConnectFailedError < FatalConnectionError; end
42
+
43
+ # Raised if the command issued is not supported by the protocol version
44
+ # negotiated between the client and broker.
45
+ class UnsupportedCommandError < StomperError; end
46
+
47
+ # An error that is raised as a result frames being generated on
48
+ # a transaction while it is in an invalid state.
49
+ # @abstract
50
+ class TransactionError < StomperError; end
51
+
52
+ # Raised if a transactionable frame is sent in a transaction that has
53
+ # already been aborted or committed.
54
+ class TransactionFinalizedError < TransactionError; end
55
+
56
+ # Raised if a BEGIN frame is sent on a transaction that has already
57
+ # begun.
58
+ class TransactionStartedError < TransactionError; end
59
+ end
@@ -0,0 +1,10 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Extensions to {Stomper::Connection} objects
4
+ module Stomper::Extensions
5
+ end
6
+
7
+ require 'stomper/extensions/common'
8
+ require 'stomper/extensions/scoping'
9
+ require 'stomper/extensions/heartbeat'
10
+ require 'stomper/extensions/events'
@@ -0,0 +1,258 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Provides the common interface for a {Stomper::Connection} object.
4
+ module Stomper::Extensions::Common
5
+ # Extends an object with any additional modules that are appropriate
6
+ # for the Stomp protocol being used.
7
+ def self.extend_by_protocol_version(instance, version)
8
+ if EXTEND_BY_VERSION[version]
9
+ EXTEND_BY_VERSION[version].each do |mod|
10
+ instance.extend mod
11
+ end
12
+ end
13
+ end
14
+
15
+
16
+ # Transmits a SEND frame to the broker with the specified destination, body
17
+ # and headers. If a block is given, a +receipt+ header will be included in the frame
18
+ # and the block will be invoked when the corresponding RECEIPT frame
19
+ # is received from the broker. The naming of this method bothers me as it
20
+ # overwrites a core Ruby method but doing so maintains the consistency of
21
+ # this interface. If you want to pass a message ala +Object#send+, use the
22
+ # +__send__+ method instead.
23
+ # @note You will need to use +__send__+ if you want the behavior of +Object#send+
24
+ # @param [String] dest the destination for the SEND frame to be delivered to
25
+ # @param [String] body the body of the SEND frame
26
+ # @param [{Symbol => String}] additional headers to include in the generated frame
27
+ # @yield [receipt] invoked when the receipt for this SEND frame has been received
28
+ # @yieldparam [Stomper::Frame] receipt the RECEIPT frame sent by the broker
29
+ # @return [Stomper::Frame] the SEND frame sent to the broker
30
+ def send(dest, body, headers={}, &block)
31
+ scoped_to = block ? with_receipt(&block) : self
32
+ scoped_to.transmit create_frame('SEND', headers, { :destination => dest }, body)
33
+ end
34
+ alias :puts :send
35
+
36
+ # Transmits a SUBSCRIBE frame to the broker with the specified destination
37
+ # and headers. If a block is given, it will be invoked every time a MESSAGE
38
+ # frame is received from the broker for this subscription.
39
+ # @param [String] dest the destination for the SEND frame to be delivered to
40
+ # @param [{Symbol => String}] additional headers to include in the generated frame
41
+ # @yield [message] invoked when a MESSAGE frame for this subscription is received
42
+ # @yieldparam [Stomper::Frame] message the MESSAGE frame sent by the broker
43
+ # @return [Stomper::Frame] the SUBSCRIBE frame sent to the broker
44
+ def subscribe(dest, headers={}, &block)
45
+ ::Stomper::Support.keys_to_sym!(headers)
46
+ if headers[:id].nil? || headers[:id].empty?
47
+ headers[:id] = ::Stomper::Support.next_serial
48
+ end
49
+ subscribe = create_frame('SUBSCRIBE', headers, { :destination => dest })
50
+ subscription_manager.add(subscribe, block) if block
51
+ transmit subscribe
52
+ end
53
+
54
+ # Transmits an UNSUBSCRIBE frame to the broker for the supplied subscription ID,
55
+ # or SUBSCRIBE frame.
56
+ # @param [Stomper::Frame, String] frame_or_id the subscription ID or SUBSCRIBE
57
+ # frame to unsubscribe from
58
+ # @return [Stomper::Frame] the UNSUBSCRIBE frame sent to the broker
59
+ # @raise [ArgumentError] if subscription ID cannot be determined.
60
+ def unsubscribe(frame_or_id, headers={})
61
+ sub_id = frame_or_id.is_a?(::Stomper::Frame) ? frame_or_id[:id] : frame_or_id
62
+ raise ArgumentError, 'subscription ID could not be determined' if sub_id.nil? || sub_id.empty?
63
+ if subscription_manager.subscribed_id? sub_id
64
+ transmit create_frame('UNSUBSCRIBE', headers, { :id => sub_id })
65
+ elsif subscription_manager.subscribed_destination? sub_id
66
+ subscription_manager.ids_for_destination(sub_id).map do |id|
67
+ transmit create_frame('UNSUBSCRIBE', headers, { :id => id })
68
+ end
69
+ end
70
+ end
71
+
72
+ # Transmits a BEGIN frame to the broker to start a transaction named by +tx_id+.
73
+ # When directly handling transaction management in this fashion, it is up to
74
+ # you to ensure the uniqueness of transaction ids, that frames within this
75
+ # transaction have their +transaction+ header set, and that transactions are
76
+ # appropriately committed or aborted.
77
+ # @see Stomper::Extensions::Scoping#with_transaction
78
+ # @see #abort
79
+ # @see #commit
80
+ # @param [String] tx_id ID of the transaction to begin
81
+ # @param [{Symbol => String}] additional headers to include in the generated frame
82
+ # @return [Stomper::Frame] the BEGIN frame sent to the broker
83
+ def begin(tx_id, headers={})
84
+ transmit create_frame('BEGIN', headers, {:transaction => tx_id})
85
+ end
86
+
87
+ # Transmits an ABORT frame to the broker to rollback a transaction named by +tx_id+.
88
+ # When directly handling transaction management in this fashion, it is up to
89
+ # you to ensure the uniqueness of transaction ids, that frames within this
90
+ # transaction have their +transaction+ header set, and that transactions are
91
+ # appropriately committed or aborted.
92
+ # @see Stomper::Extensions::Scoping#with_transaction
93
+ # @see #begin
94
+ # @see #commit
95
+ # @param [String] tx_id ID of the transaction to abort
96
+ # @param [{Symbol => String}] additional headers to include in the generated frame
97
+ # @return [Stomper::Frame] the ABORT frame sent to the broker
98
+ def abort(tx_id, headers={})
99
+ transmit create_frame('ABORT', headers, {:transaction => tx_id})
100
+ end
101
+
102
+ # Transmits a COMMIT frame to the broker to complete a transaction named by +tx_id+.
103
+ # When directly handling transaction management in this fashion, it is up to
104
+ # you to ensure the uniqueness of transaction ids, that frames within this
105
+ # transaction have their +transaction+ header set, and that transactions are
106
+ # appropriately committed or aborted.
107
+ # @see Stomper::Extensions::Scoping#with_transaction
108
+ # @see #begin
109
+ # @see #abort
110
+ # @param [String] tx_id ID of the transaction to complete
111
+ # @param [{Symbol => String}] additional headers to include in the generated frame
112
+ # @return [Stomper::Frame] the COMMIT frame sent to the broker
113
+ def commit(tx_id, headers={})
114
+ transmit create_frame('COMMIT', headers, {:transaction => tx_id})
115
+ end
116
+
117
+ # Disconnects from the broker. This is polite disconnect, in that it first
118
+ # transmits a DISCONNECT frame before closing the underlying socket. If the
119
+ # broker and client are using the Stomp 1.1 protocol, a receipt can be requested
120
+ # for the DISCONNECT frame, and the connection will remain active until
121
+ # the receipt is received or the broker closes the connection on its end.
122
+ # @param [{Symbol => String}] an optional set of headers to include in the
123
+ # DISCONNECT frame (these can include event handlers, such as :on_receipt)
124
+ def disconnect(headers={})
125
+ transmit create_frame('DISCONNECT', headers, {})
126
+ end
127
+
128
+ # Transmits an ACK frame to the broker to acknowledge that a corresponding
129
+ # MESSAGE frame has been processed by the client.
130
+ # @note If the negotiated Stomp protocol version is 1.1, this method will be
131
+ # overridden by {Stomper::Extensions::Common::V1_1#ack}
132
+ # @overload ack(message, headers={})
133
+ # @param [Stomper::Frame] message the MESSAGE frame to acknowledge
134
+ # @param [{Object => String}] headers optional headers to include with the ACK frame
135
+ # @overload ack(message_id, headers={})
136
+ # @param [String] message_id the ID of a MESSAGE frame to acknowledge
137
+ # @param [{Object => String}] headers optional headers to include with the ACK frame
138
+ # @return [Stomper::Frame] the ACK frame sent to the broker
139
+ # @example Gonna need some examples for this one...
140
+ def ack(*args)
141
+ headers = args.last.is_a?(Hash) ? args.pop : {}
142
+ m_id = args.shift
143
+ if m_id.is_a?(::Stomper::Frame)
144
+ m_id = m_id[:'message-id']
145
+ end
146
+ m_headers = [ [:'message-id', m_id] ].inject({}) do |mh, (k,v)|
147
+ mh[k] = v unless v.nil? || v.empty?
148
+ mh
149
+ end
150
+ an_frame = create_frame('ACK', headers, m_headers)
151
+ raise ::ArgumentError, 'message ID could not be determined' if an_frame[:'message-id'].nil? || an_frame[:'message-id'].empty?
152
+ transmit an_frame
153
+ end
154
+
155
+ # Always raises an error because the NACK frame is only available to connections
156
+ # using version 1.1 of the Stomp protocol.
157
+ # @note If the negotiated Stomp protocol version is 1.1, this method will be
158
+ # overridden by {Stomper::Extensions::Common::V1_1#nack}
159
+ # @see Stomper::Extensions::Protocol_1_1#nack
160
+ # @raise [Stomper::Errors::UnsupportedCommandError]
161
+ def nack(*args)
162
+ raise ::Stomper::Errors::UnsupportedCommandError, 'NACK'
163
+ end
164
+
165
+ def create_frame(command, u_head, m_head, body=nil)
166
+ ::Stomper::Frame.new(command,
167
+ ::Stomper::Support.keys_to_sym(u_head).merge(m_head), body)
168
+ end
169
+ private :create_frame
170
+
171
+ # Stomp Protocol 1.1 extensions to the common interface.
172
+ module V1_1
173
+ # Acknowledge that a MESSAGE frame has been received and successfully
174
+ # processed. The Stomp 1.1 protocol now requires that both ID of the
175
+ # message and the ID of the subscription the message arrived on must be
176
+ # specified in the ACK frame's headers.
177
+ # @overload ack(message, headers={})
178
+ # Transmit an ACK frame fro the MESSAGE frame. The appropriate
179
+ # subscription ID will be determined from the MESSAGE frame's
180
+ # +subscription+ header value.
181
+ # @param [Stomper::Frame] message the MESSAGE frame to acknowledge
182
+ # @param [{Object => String}] headers optional headers to include with the ACK frame
183
+ # @overload ack(message, subscription_id, headers={})
184
+ # Transmit an ACK frame for the MESSAGE frame, but use the supplied
185
+ # subscription ID instead of trying to determine it from the MESSAGE
186
+ # frame's headers. You should use this method of the broker you are
187
+ # connected to does not include a +subscribe+ header on MESSAGE frames.
188
+ # @param [Stomper::Frame] message the MESSAGE frame to acknowledge
189
+ # @param [String] subscription_id the ID of the subscription MESSAGE was delivered on.
190
+ # @param [{Object => String}] headers optional headers to include with the ACK frame
191
+ # @overload ack(message_id, subscription_id, headers={})
192
+ # Transmit an ACK frame for the MESSAGE frame with an ID of +message_id+
193
+ # delivered on the subscription with an ID of +subscription_id+.
194
+ # @param [String] message_id the ID of the MESSAGE frame to acknowledge
195
+ # @param [String] subscription_id the ID of the subscription MESSAGE was delivered on.
196
+ # @param [{Object => String}] headers optional headers to include with the ACK frame
197
+ # @return [Stomper::Frame] the ACK frame sent to the broker
198
+ # @raise [ArgumentError] if the message or subscription IDs cannot be
199
+ # determined
200
+ # @example Gonna need some examples for this one...
201
+ def ack(message_or_id, *args)
202
+ transmit create_ack_or_nack('ACK', message_or_id, args)
203
+ end
204
+
205
+ # Inform the broker that a MESSAGE frame was not processed. A NACK frame
206
+ # is, in effect, the opposite of an ACK frame. The NACK command is a new
207
+ # feature introduced in Stomp 1.1, hence why it is unavailable to Stomp
208
+ # 1.0 connections.
209
+ # @overload nack(message, headers={})
210
+ # Transmit a NACK frame fro the MESSAGE frame. The appropriate
211
+ # subscription ID will be determined from the MESSAGE frame's
212
+ # +subscription+ header value.
213
+ # @param [Stomper::Frame] message the MESSAGE frame to un-acknowledge
214
+ # @param [{Object => String}] headers optional headers to include with the NACK frame
215
+ # @overload nack(message, subscription_id, headers={})
216
+ # Transmit a NACK frame for the MESSAGE frame, but use the supplied
217
+ # subscription ID instead of trying to determine it from the MESSAGE
218
+ # frame's headers. You should use this method of the broker you are
219
+ # connected to does not include a +subscribe+ header on MESSAGE frames.
220
+ # @param [Stomper::Frame] message the MESSAGE frame to un-acknowledge
221
+ # @param [String] subscription_id the ID of the subscription MESSAGE was delivered on.
222
+ # @param [{Object => String}] headers optional headers to include with the NACK frame
223
+ # @overload nack(message_id, subscription_id, headers={})
224
+ # Transmit a NACK frame for the MESSAGE frame with an ID of +message_id+
225
+ # delivered on the subscription with an ID of +subscription_id+.
226
+ # @param [String] message_id the ID of the MESSAGE frame to un-acknowledge
227
+ # @param [String] subscription_id the ID of the subscription MESSAGE was delivered on.
228
+ # @param [{Object => String}] headers optional headers to include with the NACK frame
229
+ # @return [Stomper::Frame] the NACK frame sent to the broker
230
+ # @raise [ArgumentError] if the message or subscription IDs cannot be
231
+ # determined
232
+ # @example Gonna need some examples for this one...
233
+ def nack(message_or_id, *args)
234
+ transmit create_ack_or_nack('NACK', message_or_id, args)
235
+ end
236
+
237
+ def create_ack_or_nack(command, m_id, args)
238
+ headers = args.last.is_a?(Hash) ? args.pop : {}
239
+ sub_id = args.shift
240
+ if m_id.is_a?(::Stomper::Frame)
241
+ sub_id = m_id[:subscription] if sub_id.nil? || sub_id.empty?
242
+ m_id = m_id[:'message-id']
243
+ end
244
+ [[:message, m_id], [:subscription, sub_id]].each do |(k,v)|
245
+ raise ::ArgumentError, "#{k} ID could not be determined" if v.nil? || v.empty?
246
+ end
247
+ create_frame(command, headers,
248
+ {:'message-id' => m_id, :subscription => sub_id })
249
+ end
250
+ private :create_ack_or_nack
251
+ end
252
+
253
+ # A mapping between protocol versions and modules to include
254
+ EXTEND_BY_VERSION = {
255
+ '1.0' => [ ],
256
+ '1.1' => [ ::Stomper::Extensions::Common::V1_1 ]
257
+ }
258
+ end
@@ -0,0 +1,213 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Module for event based extensions.
4
+ module Stomper::Extensions::Events
5
+ # A mapping of event names that are just aliases for other event names.
6
+ ALIASED_EVENTS = {
7
+ :on_stomp => :on_connect,
8
+ :before_stomp => :before_connect,
9
+ :on_connection_disconnected => :on_connection_closed
10
+ }
11
+
12
+ # Register a callback to be fired when an ABORT frame is sent to the broker.
13
+ # @return [self]
14
+ def on_abort(&block); bind_callback(:on_abort, block); end
15
+ # Register a callback to be fired before an ABORT frame is sent to the broker.
16
+ # @return [self]
17
+ def before_abort(&block); bind_callback(:before_abort, block); end
18
+
19
+ # Register a callback to be fired when an ACK frame is sent to the broker.
20
+ # @return [self]
21
+ def on_ack(&block); bind_callback(:on_ack, block); end
22
+ # Register a callback to be fired before an ACK frame is sent to the broker.
23
+ # @return [self]
24
+ def before_ack(&block); bind_callback(:before_ack, block); end
25
+
26
+ # Register a callback to be fired when a BEGIN frame is sent to the broker.
27
+ # @return [self]
28
+ def on_begin(&block); bind_callback(:on_begin, block); end
29
+ # Register a callback to be fired before a BEGIN frame is sent to the broker.
30
+ # @return [self]
31
+ def before_begin(&block); bind_callback(:before_begin, block); end
32
+
33
+ # Register a callback to be fired when a COMMIT frame is sent to the broker.
34
+ # @return [self]
35
+ def on_commit(&block); bind_callback(:on_commit, block); end
36
+ # Register a callback to be fired before a COMMIT frame is sent to the broker.
37
+ # @return [self]
38
+ def before_commit(&block); bind_callback(:before_commit, block); end
39
+
40
+ # Register a callback to be fired when a CONNECT frame is sent to the broker.
41
+ # @return [self]
42
+ def on_connect(&block); bind_callback(:on_connect, block); end
43
+ # Register a callback to be fired before a CONNECT frame is sent to the broker.
44
+ # @return [self]
45
+ def before_connect(&block); bind_callback(:before_connect, block); end
46
+ alias :on_stomp :on_connect
47
+ alias :before_stomp :before_connect
48
+
49
+ # Register a callback to be fired when a CONNECTED frame is received from the broker.
50
+ # @return [self]
51
+ def on_connected(&block); bind_callback(:on_connected, block); end
52
+
53
+ # Register a callback to be fired when a DISCONNECT frame is sent to the broker.
54
+ # @return [self]
55
+ def on_disconnect(&block); bind_callback(:on_disconnect, block); end
56
+ # Register a callback to be fired before a DISCONNECT frame is sent to the broker.
57
+ # @return [self]
58
+ def before_disconnect(&block); bind_callback(:before_disconnect, block); end
59
+
60
+ # Register a callback to be fired when an ERROR frame is received from the broker.
61
+ # @return [self]
62
+ def on_error(&block); bind_callback(:on_error, block); end
63
+
64
+ # Register a callback to be fired when a MESSAGE frame is received from the broker.
65
+ # @return [self]
66
+ def on_message(&block); bind_callback(:on_message, block); end
67
+
68
+ # Register a callback to be fired when a NACK frame is sent to the broker.
69
+ # @return [self]
70
+ def on_nack(&block); bind_callback(:on_nack, block); end
71
+ # Register a callback to be fired before a NACK frame is sent to the broker.
72
+ # @return [self]
73
+ def before_nack(&block); bind_callback(:before_nack, block); end
74
+
75
+ # Register a callback to be fired when a RECEIPT frame is received from the broker.
76
+ # @return [self]
77
+ def on_receipt(&block); bind_callback(:on_receipt, block); end
78
+
79
+ # Register a callback to be fired when a SEND frame is sent to the broker.
80
+ # @return [self]
81
+ def on_send(&block); bind_callback(:on_send, block); end
82
+ # Register a callback to be fired before a SEND frame is sent to the broker.
83
+ # @return [self]
84
+ def before_send(&block); bind_callback(:before_send, block); end
85
+
86
+ # Register a callback to be fired when a SUBSCRIBE frame is sent to the broker.
87
+ # @return [self]
88
+ def on_subscribe(&block); bind_callback(:on_subscribe, block); end
89
+ # Register a callback to be fired before a SUBSCRIBE frame is sent to the broker.
90
+ # @return [self]
91
+ def before_subscribe(&block); bind_callback(:before_subscribe, block); end
92
+
93
+ # Register a callback to be fired when an UNSUBSCRIBE frame is sent to the broker.
94
+ # @return [self]
95
+ def on_unsubscribe(&block); bind_callback(:on_unsubscribe, block); end
96
+ # Register a callback to be fired before an UNSUBSCRIBE frame is sent to the broker.
97
+ # @return [self]
98
+ def before_unsubscribe(&block); bind_callback(:before_unsubscribe, block); end
99
+
100
+ # Register a callback to be fired when a heartbeat is sent to the broker.
101
+ # @return [self]
102
+ def on_client_beat(&block); bind_callback(:on_client_beat, block); end
103
+ # Register a callback to be fired before a heartbeat frame is sent to the broker.
104
+ # @return [self]
105
+ def before_client_beat(&block); bind_callback(:before_client_beat, block); end
106
+
107
+ # Register a callback to be fired when a heartbeat is received from the
108
+ # broker.
109
+ # @return [self]
110
+ def on_broker_beat(&block); bind_callback(:on_broker_beat, block); end
111
+
112
+ # Register a callback to be fired when a connection to the broker has
113
+ # been fully established. The connection is fully established once the
114
+ # client has sent a CONNECT frame, the broker has replied with CONNECTED
115
+ # and protocol versions and heartbeat strategies have been negotiated (if
116
+ # applicable.)
117
+ # @return [self]
118
+ def on_connection_established(&block); bind_callback(:on_connection_established, block); end
119
+
120
+ # Register a callback to be fired when a connection to the broker has
121
+ # been closed. This event will be triggered by
122
+ # {Stomper::Connection#disconnect} as well as any IO exception that shuts
123
+ # the connection down. In the event that the socket closes unexpectedly,
124
+ # {#on_connection_terminated} will be triggered before this event.
125
+ # @see #on_connection_terminated
126
+ # @return [self]
127
+ def on_connection_closed(&block); bind_callback(:on_connection_closed, block); end
128
+ alias :on_connection_disconnected :on_connection_closed
129
+
130
+ # Register a callback to be fired when a connection to the broker has
131
+ # died as per the negotiated heartbeat strategy. This event is triggered
132
+ # through {Stomper::Connection#transmit} and {Stomper::Connection#receive}
133
+ # when heartbeat death has been detected. You should not expect this event
134
+ # to trigger at the precise moment the heartbeat strategy failed.
135
+ # @note This event is not triggered the moment heartbeat death occurs.
136
+ # @return [self]
137
+ def on_connection_died(&block); bind_callback(:on_connection_died, block); end
138
+
139
+ # Register a callback to be fired when a connection to the broker has
140
+ # been unexpectedly terminated. This event will NOT be triggered by
141
+ # {Stomper::Connection#disconnect}.
142
+ # @see #on_connection_closed
143
+ # @return [self]
144
+ def on_connection_terminated(&block); bind_callback(:on_connection_terminated, block); end
145
+
146
+ # Register a callback to be fired before transmitting any frame. If the
147
+ # supplied block makes any changes to the frame argument, those changes
148
+ # will be sent to the remaining #before_transmitting callbacks, and
149
+ # ultimately will be passed on to the broker. This provides a convenient
150
+ # way to modify frames before transmission without having to subclass or
151
+ # otherwise extend the {Stomper::Connection} class. Furhter,
152
+ # changing the {Stomper::Frame#command command} attribute of the frame
153
+ # will change the frame-specific event that is triggered.
154
+ # @return [self]
155
+ def before_transmitting(&block); bind_callback(:before_transmitting, block); end
156
+
157
+ # Register a callback to be fired after transmitting any frame.
158
+ # Changes made to the frame object will be passed along to all remaining
159
+ # {#after_transmitting} callbacks. Furhter,
160
+ # changing the {Stomper::Frame#command command} attribute of the frame
161
+ # will change the frame-specific event that is triggered.
162
+ # @return [self]
163
+ def after_transmitting(&block); bind_callback(:after_transmitting, block); end
164
+
165
+ # Register a callback to be fired before receiving any frame. As a frame
166
+ # has not yet been received, callbacks invoked on this event will have
167
+ # to work with very limited information.
168
+ # @return [self]
169
+ def before_receiving(&block); bind_callback(:before_receiving, block); end
170
+
171
+ # Register a callback to be fired after receiving any frame. Like
172
+ # the #before_transmitting event, any changes made to the frame will be
173
+ # passed along to all remaining {#after_receiving} callbacks. Furhter,
174
+ # changing the {Stomper::Frame#command command} attribute of the frame
175
+ # will change the frame-specific event that is triggered.
176
+ # @return [self]
177
+ def after_receiving(&block); bind_callback(:after_receiving, block); end
178
+
179
+ # Binds a +Proc+ to be invoked when the given +event_name+ is triggered.
180
+ # @param [Symbol] event_name
181
+ # @param [Proc] cb_proc
182
+ # @return [self]
183
+ def bind_callback(event_name, cb_proc)
184
+ @event_callbacks ||= {}
185
+ @event_callbacks[event_name] ||= []
186
+ @event_callbacks[event_name] << cb_proc
187
+ self
188
+ end
189
+
190
+ def trigger_event(event_name, *args)
191
+ event_name = ALIASED_EVENTS[event_name] ? ALIASED_EVENTS[event_name] : event_name
192
+ @event_callbacks[event_name] && @event_callbacks[event_name].each { |cb| cb.call(*args) }
193
+ end
194
+ private :trigger_event
195
+
196
+ def trigger_received_frame(frame, *args); trigger_frame(frame, :on, :on_broker_beat, args); end
197
+ private :trigger_received_frame
198
+
199
+ def trigger_transmitted_frame(frame, *args); trigger_frame(frame, :on, :on_client_beat, args); end
200
+ private :trigger_transmitted_frame
201
+
202
+ def trigger_before_transmitted_frame(frame, *args); trigger_frame(frame, :before, :before_client_beat, args); end
203
+ private :trigger_before_transmitted_frame
204
+
205
+ def trigger_frame(frame, timing, beat_event, args)
206
+ if (f_comm = frame.command && frame.command.downcase.to_sym)
207
+ trigger_event(:"#{timing}_#{f_comm}", frame, *args)
208
+ else
209
+ trigger_event(beat_event, frame, *args)
210
+ end
211
+ end
212
+ private :trigger_frame
213
+ end