stomper 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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