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.
- data/.gitignore +5 -0
- data/{spec/spec.opts → .rspec} +0 -2
- data/Gemfile +4 -0
- data/LICENSE +201 -201
- data/README.md +130 -0
- data/Rakefile +5 -0
- data/examples/basic.rb +38 -0
- data/examples/events.rb +54 -0
- data/features/acking_messages.feature +147 -0
- data/features/disconnecting.feature +12 -0
- data/features/establish_connection.feature +44 -0
- data/features/protocol_version_negotiation.feature +61 -0
- data/features/receipts.feature +72 -0
- data/features/scopes.feature +32 -0
- data/features/secure_connections.feature +38 -0
- data/features/send_and_message.feature +28 -0
- data/features/steps/acking_messages_steps.rb +39 -0
- data/features/steps/disconnecting_steps.rb +8 -0
- data/features/steps/establish_connection_steps.rb +74 -0
- data/features/steps/frame_transmission_steps.rb +35 -0
- data/features/steps/protocol_version_negotiation_steps.rb +15 -0
- data/features/steps/receipts_steps.rb +79 -0
- data/features/steps/scopes_steps.rb +52 -0
- data/features/steps/secure_connections_steps.rb +41 -0
- data/features/steps/send_and_message_steps.rb +35 -0
- data/features/steps/subscribing_steps.rb +36 -0
- data/features/steps/threaded_receiver_steps.rb +8 -0
- data/features/steps/transactions_steps.rb +0 -0
- data/features/subscribing.feature +151 -0
- data/features/support/env.rb +11 -0
- data/features/support/header_helpers.rb +12 -0
- data/features/support/ssl/README +6 -0
- data/features/support/ssl/broker_cert.csr +17 -0
- data/features/support/ssl/broker_cert.pem +72 -0
- data/features/support/ssl/broker_key.pem +27 -0
- data/features/support/ssl/client_cert.csr +17 -0
- data/features/support/ssl/client_cert.pem +72 -0
- data/features/support/ssl/client_key.pem +27 -0
- data/features/support/ssl/demoCA/cacert.pem +17 -0
- data/features/support/ssl/demoCA/index.txt +2 -0
- data/features/support/ssl/demoCA/index.txt.attr +1 -0
- data/features/support/ssl/demoCA/index.txt.attr.old +1 -0
- data/features/support/ssl/demoCA/index.txt.old +1 -0
- data/features/support/ssl/demoCA/newcerts/01.pem +72 -0
- data/features/support/ssl/demoCA/newcerts/02.pem +72 -0
- data/features/support/ssl/demoCA/private/cakey.pem +17 -0
- data/features/support/ssl/demoCA/serial +1 -0
- data/features/support/ssl/demoCA/serial.old +1 -0
- data/features/support/test_stomp_server.rb +150 -0
- data/features/threaded_receiver.feature +11 -0
- data/features/transactions.feature +66 -0
- data/lib/stomper.rb +30 -20
- data/lib/stomper/connection.rb +442 -102
- data/lib/stomper/errors.rb +59 -0
- data/lib/stomper/extensions.rb +10 -0
- data/lib/stomper/extensions/common.rb +258 -0
- data/lib/stomper/extensions/events.rb +213 -0
- data/lib/stomper/extensions/heartbeat.rb +101 -0
- data/lib/stomper/extensions/scoping.rb +56 -0
- data/lib/stomper/frame.rb +54 -0
- data/lib/stomper/frame_serializer.rb +217 -0
- data/lib/stomper/headers.rb +15 -0
- data/lib/stomper/receipt_manager.rb +36 -0
- data/lib/stomper/receivers.rb +7 -0
- data/lib/stomper/receivers/threaded.rb +71 -0
- data/lib/stomper/scopes.rb +9 -0
- data/lib/stomper/scopes/header_scope.rb +49 -0
- data/lib/stomper/scopes/receipt_scope.rb +44 -0
- data/lib/stomper/scopes/transaction_scope.rb +109 -0
- data/lib/stomper/sockets.rb +66 -28
- data/lib/stomper/subscription_manager.rb +79 -0
- data/lib/stomper/support.rb +68 -0
- data/lib/stomper/support/1.8/frame_serializer.rb +53 -0
- data/lib/stomper/support/1.8/headers.rb +183 -0
- data/lib/stomper/support/1.9/frame_serializer.rb +64 -0
- data/lib/stomper/support/1.9/headers.rb +172 -0
- data/lib/stomper/support/ruby.rb +13 -0
- data/lib/stomper/uris.rb +49 -0
- data/lib/stomper/version.rb +7 -0
- data/spec/spec_helper.rb +13 -9
- data/spec/stomper/connection_spec.rb +712 -0
- data/spec/stomper/extensions/common_spec.rb +187 -0
- data/spec/stomper/extensions/events_spec.rb +78 -0
- data/spec/stomper/extensions/heartbeat_spec.rb +103 -0
- data/spec/stomper/extensions/scoping_spec.rb +21 -0
- data/spec/stomper/frame_serializer_1.8_spec.rb +318 -0
- data/spec/stomper/frame_serializer_spec.rb +316 -0
- data/spec/stomper/frame_spec.rb +36 -0
- data/spec/stomper/headers_spec.rb +224 -0
- data/spec/stomper/receipt_manager_spec.rb +91 -0
- data/spec/stomper/receivers/threaded_spec.rb +116 -0
- data/spec/stomper/scopes/header_scope_spec.rb +42 -0
- data/spec/stomper/scopes/receipt_scope_spec.rb +51 -0
- data/spec/stomper/scopes/transaction_scope_spec.rb +183 -0
- data/spec/stomper/sockets_spec.rb +113 -0
- data/spec/stomper/subscription_manager_spec.rb +107 -0
- data/spec/stomper/support_spec.rb +69 -0
- data/spec/stomper/uris_spec.rb +54 -0
- data/spec/stomper_spec.rb +9 -0
- data/spec/support/custom_argument_matchers.rb +57 -0
- data/spec/support/existential_frame_matchers.rb +19 -0
- data/spec/support/frame_header_matchers.rb +10 -0
- data/stomper.gemspec +30 -0
- metadata +272 -97
- data/AUTHORS +0 -21
- data/CHANGELOG +0 -20
- data/README.rdoc +0 -120
- data/lib/stomper/client.rb +0 -34
- data/lib/stomper/frame_reader.rb +0 -73
- data/lib/stomper/frame_writer.rb +0 -21
- data/lib/stomper/frames.rb +0 -39
- data/lib/stomper/frames/abort.rb +0 -10
- data/lib/stomper/frames/ack.rb +0 -25
- data/lib/stomper/frames/begin.rb +0 -11
- data/lib/stomper/frames/client_frame.rb +0 -89
- data/lib/stomper/frames/commit.rb +0 -10
- data/lib/stomper/frames/connect.rb +0 -10
- data/lib/stomper/frames/connected.rb +0 -30
- data/lib/stomper/frames/disconnect.rb +0 -10
- data/lib/stomper/frames/error.rb +0 -21
- data/lib/stomper/frames/message.rb +0 -48
- data/lib/stomper/frames/receipt.rb +0 -19
- data/lib/stomper/frames/send.rb +0 -10
- data/lib/stomper/frames/server_frame.rb +0 -38
- data/lib/stomper/frames/subscribe.rb +0 -42
- data/lib/stomper/frames/unsubscribe.rb +0 -19
- data/lib/stomper/open_uri_interface.rb +0 -41
- data/lib/stomper/receipt_handlers.rb +0 -23
- data/lib/stomper/receiptor.rb +0 -38
- data/lib/stomper/subscriber.rb +0 -76
- data/lib/stomper/subscription.rb +0 -128
- data/lib/stomper/subscriptions.rb +0 -95
- data/lib/stomper/threaded_receiver.rb +0 -59
- data/lib/stomper/transaction.rb +0 -185
- data/lib/stomper/transactor.rb +0 -50
- data/lib/stomper/uri.rb +0 -55
- data/spec/client_spec.rb +0 -29
- data/spec/connection_spec.rb +0 -22
- data/spec/frame_reader_spec.rb +0 -37
- data/spec/frame_writer_spec.rb +0 -27
- data/spec/frames/client_frame_spec.rb +0 -66
- data/spec/frames/indirect_frame_spec.rb +0 -45
- data/spec/frames/server_frame_spec.rb +0 -85
- data/spec/open_uri_interface_spec.rb +0 -132
- data/spec/receiptor_spec.rb +0 -35
- data/spec/shared_connection_examples.rb +0 -79
- data/spec/subscriber_spec.rb +0 -77
- data/spec/subscription_spec.rb +0 -157
- data/spec/subscriptions_spec.rb +0 -145
- data/spec/threaded_receiver_spec.rb +0 -33
- data/spec/transaction_spec.rb +0 -139
- data/spec/transactor_spec.rb +0 -46
data/lib/stomper/subscription.rb
DELETED
@@ -1,128 +0,0 @@
|
|
1
|
-
module Stomper
|
2
|
-
# A representation of a subscription to a stomp broker destination. The
|
3
|
-
# attributes +id+, +destination+, +ack+ and +selector+ have the same
|
4
|
-
# semantic meaning as the headers of a Stomp "SUBSCRIBE" frame with the same
|
5
|
-
# name.
|
6
|
-
class Subscription
|
7
|
-
attr_reader :id, :destination, :ack, :selector
|
8
|
-
|
9
|
-
# Creates a new Subscription instance from the given parameters.
|
10
|
-
# The +destination_or_options+ parameter can either be a string
|
11
|
-
# specification of the destination, such as "/queue/target", or a hash
|
12
|
-
# corresponding to the headers of a "SUBSCRIBE" frame
|
13
|
-
# (eg: { :destination => "/queue/target", :id => "sub-001", ... })
|
14
|
-
#
|
15
|
-
# The optional +subscription_id+ parameter is a string corresponding
|
16
|
-
# to the name of this subscription. If this parameter is specified, it
|
17
|
-
# should be unique within the context of a given Stomper::Client, otherwise
|
18
|
-
# the behavior of the Stomper::Client#unsubscribe method may have unintended
|
19
|
-
# consequences.
|
20
|
-
#
|
21
|
-
# The optional +ack+ parameter specifies the mode that a client
|
22
|
-
# will use to acknowledge received messages and may be either :client or :auto.
|
23
|
-
# The default, :auto, does not require the client to notify the broker when
|
24
|
-
# it has received a message; however, setting +ack+ to :client will require
|
25
|
-
# each message received by this subscription to be acknowledged through the
|
26
|
-
# use of Stomper::Client#ack in order to ensure proper interaction between
|
27
|
-
# client and broker.
|
28
|
-
#
|
29
|
-
# The +selector+ parameter (again, optional) sets a SQL 92 selector for
|
30
|
-
# this subscription with the stomp broker as per the Stomp Protocol specification.
|
31
|
-
# Support of this functionality is entirely the responsibility of the broker,
|
32
|
-
# there is no client side filtering being done on incoming messages.
|
33
|
-
#
|
34
|
-
# When a message is "received" by an instance of Subscription, the supplied
|
35
|
-
# +block+ is inovked with the received message sent as a parameter.
|
36
|
-
#
|
37
|
-
# If no +subscription_id+ is specified, either explicitly or through a
|
38
|
-
# hash key of 'id' in +destination_or_options+, one may be automatically
|
39
|
-
# generated of the form +sub-<Time.now.to_f>+. The automatic generation
|
40
|
-
# of a subscription id occurs if and only if naive? returns false.
|
41
|
-
#
|
42
|
-
# While direct creation of Subscription instances is possible, the preferred
|
43
|
-
# method is for them to be constructed by a Stomper::Client through the use
|
44
|
-
# of the Stomper::Client#subscribe method.
|
45
|
-
#
|
46
|
-
# @see naive?
|
47
|
-
# @see Subscriber#subscribe
|
48
|
-
# @see Subscriber#unsubscribe
|
49
|
-
# @see Client#ack
|
50
|
-
def initialize(destination_or_options, subscription_id=nil, ack=nil, selector=nil, &block)
|
51
|
-
if destination_or_options.is_a?(Hash)
|
52
|
-
destination = destination_or_options[:destination]
|
53
|
-
subscription_id ||= destination_or_options[:id]
|
54
|
-
ack ||= destination_or_options[:ack]
|
55
|
-
selector ||= destination_or_options[:selector]
|
56
|
-
else
|
57
|
-
destination = destination_or_options.to_s
|
58
|
-
end
|
59
|
-
@id = subscription_id
|
60
|
-
@destination = destination
|
61
|
-
@ack = (ack || :auto).to_sym
|
62
|
-
@selector = selector
|
63
|
-
@call_back = block
|
64
|
-
@id ||= "sub-#{Time.now.to_f}" unless naive?
|
65
|
-
end
|
66
|
-
|
67
|
-
# Returns true if this subscription has no explicitly specified id,
|
68
|
-
# has no selector specified, and acknowledges messages through the :auto
|
69
|
-
# mode.
|
70
|
-
def naive?
|
71
|
-
@id.nil? && @selector.nil? && @ack == :auto
|
72
|
-
end
|
73
|
-
|
74
|
-
# Returns true if this subscription is responsible for a Stomper::Client
|
75
|
-
# instance receiving +message_frame+.
|
76
|
-
#
|
77
|
-
# See also: receives_for?, perform
|
78
|
-
def accepts?(message_frame)
|
79
|
-
receives_for?(message_frame.destination, message_frame.subscription)
|
80
|
-
end
|
81
|
-
|
82
|
-
# Returns true if this subscription is responsible for receiving
|
83
|
-
# messages for the given destination or subscription id, specified
|
84
|
-
# by +dest+ and +subid+ respectively.
|
85
|
-
#
|
86
|
-
# Note: if +subid+ is non-nil or this subscription is not naive?,
|
87
|
-
# then this method returns true if and only if the supplied +subid+ is
|
88
|
-
# equal to the +id+ of this subscription. Otherwise, the return value
|
89
|
-
# depends only upon the equality of +dest+ and this subscriptions +destination+
|
90
|
-
# attribute.
|
91
|
-
#
|
92
|
-
# See also: naive?
|
93
|
-
def receives_for?(dest, subid=nil)
|
94
|
-
if naive? && subid.nil?
|
95
|
-
@destination == dest
|
96
|
-
else
|
97
|
-
@id == subid
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# Invokes the block associated with this subscription if
|
102
|
-
# this subscription accepts the supplied +message_frame+.
|
103
|
-
#
|
104
|
-
# See also: accepts?
|
105
|
-
def perform(message_frame)
|
106
|
-
@call_back.call(message_frame) if accepts?(message_frame)
|
107
|
-
end
|
108
|
-
|
109
|
-
# Converts this representation of a subscription into a
|
110
|
-
# Stomper::Frames::Subscribe client frame that can be transmitted
|
111
|
-
# to a stomp broker through a Stomper::Connection instance.
|
112
|
-
def to_subscribe
|
113
|
-
headers = { :destination => @destination, :ack => @ack.to_s }
|
114
|
-
headers[:id] = @id unless @id.nil?
|
115
|
-
headers[:selector] = @selector unless @selector.nil?
|
116
|
-
Stomper::Frames::Subscribe.new(@destination, headers)
|
117
|
-
end
|
118
|
-
|
119
|
-
# Converts this representation of a subscription into a
|
120
|
-
# Stomper::Frames::Unsubscribe client frame that can be transmitted
|
121
|
-
# to a stomp broker through a Stomper::Connection instance.
|
122
|
-
def to_unsubscribe
|
123
|
-
headers = { :destination => @destination }
|
124
|
-
headers[:id] = @id unless @id.nil?
|
125
|
-
Stomper::Frames::Unsubscribe.new(@destination, headers)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
@@ -1,95 +0,0 @@
|
|
1
|
-
module Stomper
|
2
|
-
# A Subscription collection class used internally by Stomper::Client to store
|
3
|
-
# its subscriptions. Instances of this class utilize synchronization making
|
4
|
-
# it safe to use in a multi-threaded context.
|
5
|
-
class Subscriptions
|
6
|
-
include Enumerable
|
7
|
-
|
8
|
-
# Creates a new Subscriptions container.
|
9
|
-
def initialize
|
10
|
-
@subs = []
|
11
|
-
@sub_lock = Mutex.new
|
12
|
-
end
|
13
|
-
|
14
|
-
# Adds the supplied subscription, +sub+, to the collection.
|
15
|
-
def <<(sub)
|
16
|
-
add(sub)
|
17
|
-
end
|
18
|
-
|
19
|
-
# Adds the supplied subscription, +sub+, to the collection.
|
20
|
-
def add(sub)
|
21
|
-
raise ArgumentError, "appended object must be a subscription" unless sub.is_a?(Subscription)
|
22
|
-
@sub_lock.synchronize { @subs << sub }
|
23
|
-
end
|
24
|
-
|
25
|
-
# Removes all Subscription objects from the collection that match
|
26
|
-
# the supplied destination, +dest+, and subscription id, +subid+.
|
27
|
-
# If +dest+ is a hash, the value referenced by the :destination key
|
28
|
-
# will be used as the destination, and +subid+ will be set to the value
|
29
|
-
# referenced by :id, unless it is explicitly set beforehand. If +dest+ is
|
30
|
-
# an instance of Subscription, the +destination+ attribute will be used
|
31
|
-
# as the destination, and +subid+ will be set to the +id+ attribute, unless
|
32
|
-
# explicitly set beforehand. The Subscription objects removed are all of
|
33
|
-
# those, and only those, for which the Stomper::Subscription#receives_for?
|
34
|
-
# method returns true given the destination and/or subscription id.
|
35
|
-
#
|
36
|
-
# This method returns an array of all the Subscription objects that were
|
37
|
-
# removed, or an empty array if none were removed.
|
38
|
-
#
|
39
|
-
# See also: Stomper::Subscription#receives_for?, Stomper::Client#unsubscribe
|
40
|
-
def remove(dest, subid=nil)
|
41
|
-
if dest.is_a?(Hash)
|
42
|
-
subid ||= dest[:id]
|
43
|
-
dest = dest[:destination]
|
44
|
-
elsif dest.is_a?(Subscription)
|
45
|
-
subid ||= dest.id
|
46
|
-
dest = dest.destination
|
47
|
-
end
|
48
|
-
_remove(dest, subid)
|
49
|
-
end
|
50
|
-
|
51
|
-
# Returns the number of Subscription objects within the container through
|
52
|
-
# the use of synchronization.
|
53
|
-
def size
|
54
|
-
@sub_lock.synchronize { @subs.size }
|
55
|
-
end
|
56
|
-
|
57
|
-
# Returns the first Subscription object within the container through
|
58
|
-
# the use of synchronization.
|
59
|
-
def first
|
60
|
-
@sub_lock.synchronize { @subs.first }
|
61
|
-
end
|
62
|
-
|
63
|
-
# Returns the last Subscription object within the container through
|
64
|
-
# the use of synchronization.
|
65
|
-
def last
|
66
|
-
@sub_lock.synchronize { @subs.last }
|
67
|
-
end
|
68
|
-
|
69
|
-
# Evaluates the supplied +block+ for each Subscription object
|
70
|
-
# within the container, or yields an Enumerator for the collection
|
71
|
-
# if no +block+ is given. As this method is synchronized, it is
|
72
|
-
# entirely possible to enter into a dead-lock if the supplied block
|
73
|
-
# in turn calls any other synchronized method of the container.
|
74
|
-
# [This could be remedied by creating a new array with
|
75
|
-
# the same Subscription objects currently contained, and performing
|
76
|
-
# the +each+ call on the new array. Give this some thought.]
|
77
|
-
def each(&block)
|
78
|
-
@sub_lock.synchronize { @subs.each(&block) }
|
79
|
-
end
|
80
|
-
|
81
|
-
# Passes the supplied +message+ to all Subscription objects within the
|
82
|
-
# collection through their Stomper::Subscription#perform method.
|
83
|
-
def perform(message)
|
84
|
-
@sub_lock.synchronize { @subs.each { |sub| sub.perform(message) } }
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
def _remove(dest, subid)
|
89
|
-
@sub_lock.synchronize do
|
90
|
-
to_remove, @subs = @subs.partition { |s| s.receives_for?(dest,subid) }
|
91
|
-
to_remove
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
module Stomper
|
2
|
-
module ThreadedReceiver
|
3
|
-
def self.extended(base)
|
4
|
-
base.instance_eval do
|
5
|
-
@receiver_mutex = Mutex.new
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
# Starts the threaded receiver on a connection, calling receive
|
10
|
-
# on the connection repeatedly in a separate thread until the receiver
|
11
|
-
# is stopped or the connection is closed.
|
12
|
-
#
|
13
|
-
# @return self
|
14
|
-
# @see ThreadedReceiver#stop
|
15
|
-
# @see Connection#receive
|
16
|
-
# @see Connection#connected?
|
17
|
-
def start(opts={})
|
18
|
-
connect unless connected?
|
19
|
-
do_start = false
|
20
|
-
@receiver_mutex.synchronize do
|
21
|
-
do_start = !started?
|
22
|
-
end
|
23
|
-
if do_start
|
24
|
-
@started = true
|
25
|
-
@run_thread = Thread.new() do
|
26
|
-
while started? && connected?
|
27
|
-
receive
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
self
|
32
|
-
end
|
33
|
-
|
34
|
-
# Stops the threaded receiver on a connection thereby stopping further
|
35
|
-
# calls to receive.
|
36
|
-
#
|
37
|
-
# @return self
|
38
|
-
# @see ThreadedReceiver#start
|
39
|
-
# @see Connection#receive
|
40
|
-
# @see Connection#connected?
|
41
|
-
def stop
|
42
|
-
do_stop = false
|
43
|
-
@receiver_mutex.synchronize do
|
44
|
-
do_stop = started?
|
45
|
-
end
|
46
|
-
if do_stop
|
47
|
-
@started = false
|
48
|
-
@run_thread.join
|
49
|
-
@run_thread = nil
|
50
|
-
end
|
51
|
-
self
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
def started?
|
56
|
-
@started
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
data/lib/stomper/transaction.rb
DELETED
@@ -1,185 +0,0 @@
|
|
1
|
-
module Stomper
|
2
|
-
# An exception raised whenever a Transaction object has been aborted
|
3
|
-
# due to an unhandled exception generated by its supplied block, or when
|
4
|
-
# the block explicitly aborts the transaction.
|
5
|
-
#
|
6
|
-
# See also: Stomper::Transaction#perform
|
7
|
-
class TransactionAborted < RuntimeError; end
|
8
|
-
|
9
|
-
# An encapsulation of a stomp transaction. Manually managing transactions
|
10
|
-
# is possible through the use of Stomper::Client#begin, Stomper::Client#commit,
|
11
|
-
# and Stomper::Client#abort.
|
12
|
-
#
|
13
|
-
# === Example Usage
|
14
|
-
#
|
15
|
-
# When the transaction is passed to the block:
|
16
|
-
#
|
17
|
-
# client.transaction do |t|
|
18
|
-
# t.send("/queue/target", "doing some work")
|
19
|
-
#
|
20
|
-
# # do something that might raise an exception, indicating that any
|
21
|
-
# # messages and acknowledgements we have sent should be "undone"
|
22
|
-
#
|
23
|
-
# t.send("/queue/target", "completed work")
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# When the block is evaluated within the transaction:
|
27
|
-
#
|
28
|
-
# client.transaction do
|
29
|
-
# send("/queue/target", "doing some work")
|
30
|
-
#
|
31
|
-
# # ...
|
32
|
-
#
|
33
|
-
# send("/queue/target", "completed work")
|
34
|
-
# end
|
35
|
-
#
|
36
|
-
# Nesting transactions:
|
37
|
-
#
|
38
|
-
# client.transaction do |t|
|
39
|
-
# t.transaction do |nt|
|
40
|
-
# nt.send("/queue/target", ...)
|
41
|
-
#
|
42
|
-
# nt.transaction do |nnt|
|
43
|
-
# nnt.send("/queue/target", ...)
|
44
|
-
#
|
45
|
-
# # do something with potentially exceptional results
|
46
|
-
# end
|
47
|
-
#
|
48
|
-
# nt.send("/queue/target", ...)
|
49
|
-
# end
|
50
|
-
#
|
51
|
-
# t.send("/queue/target", ...)
|
52
|
-
# end
|
53
|
-
#
|
54
|
-
# See also: Stomper::Client#transaction
|
55
|
-
#
|
56
|
-
class Transaction
|
57
|
-
# The id of this transaction, used to reference the transaction with the stomp broker.
|
58
|
-
attr_reader :id
|
59
|
-
|
60
|
-
# Creates a new Transaction instance. The +client+ parameter
|
61
|
-
# is an instance of Stomper::Client and is required so that the Transaction
|
62
|
-
# instance has somewhere to forward +begin+, +ack+ and +abort+ methods
|
63
|
-
# to. If the +trans_id+ parameter is not specified, an id is automatically
|
64
|
-
# generated of the form +tx-<Time.now.to_f>+. This name can be accessed
|
65
|
-
# through the +id+ attribute and is used in naming the transaction to
|
66
|
-
# the stomp broker. If +block+ is given, the Transaction instance immediately
|
67
|
-
# calls its perform method with the supplied +block+.
|
68
|
-
def initialize(client, trans_id=nil, &block)
|
69
|
-
@client = client
|
70
|
-
@id = trans_id || "tx-#{Time.now.to_f}"
|
71
|
-
@committed = false
|
72
|
-
@aborted = false
|
73
|
-
perform(&block) if block_given?
|
74
|
-
end
|
75
|
-
|
76
|
-
# Invokes the given +block+. If the +block+ executes normally, the
|
77
|
-
# transaction is committed, otherwise it is aborted.
|
78
|
-
# If +block+ accepts a parameter, this method yields itself to the block,
|
79
|
-
# otherwise, +block+ is evaluated within the context of this instance through
|
80
|
-
# +instance_eval+.
|
81
|
-
#
|
82
|
-
# If a call to +abort+ is issued within the block, the transaction is aborted
|
83
|
-
# as demanded, and no attempt is made to commit it; however, no code after the
|
84
|
-
# call to +abort+ will be evaluated, as +abort+ raises a TransactionAborted
|
85
|
-
# exception.
|
86
|
-
#
|
87
|
-
# If a call to +commit+ is issued within the block, the transaction is committed
|
88
|
-
# as demanded, and no attempt is made to commit it after +block+ has finished
|
89
|
-
# executing. As +commit+ does not raise an excpetion, all code after the call
|
90
|
-
# to commit will be evaluated.
|
91
|
-
#
|
92
|
-
# If you are using Transaction objects directly, and not relying on their
|
93
|
-
# generation through Stomper::Client#transaction, be warned that this method
|
94
|
-
# will raise a TransactionAborted exception if the +block+ evaluation fails.
|
95
|
-
# This behavior allows for nesting transactions and ensuring that if a nested
|
96
|
-
# transaction fails, so do all of its ancestors.
|
97
|
-
#
|
98
|
-
# @param [Proc] block A block of code that is evaluated as part of the transaction.
|
99
|
-
# @raise [TransactionAborted] raises an exception if the given block raises an exception
|
100
|
-
def perform(&block) #:yields: transaction
|
101
|
-
begin
|
102
|
-
@client.begin(@id)
|
103
|
-
if block.arity == 1
|
104
|
-
yield self
|
105
|
-
else
|
106
|
-
instance_eval(&block)
|
107
|
-
end
|
108
|
-
commit
|
109
|
-
rescue => err
|
110
|
-
_abort
|
111
|
-
raise TransactionAborted, "aborted transaction '#{@id}' originator: #{err.to_s}"
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
# Returns true if the Transaction object has already been committed, false
|
116
|
-
# otherwise.
|
117
|
-
def committed?
|
118
|
-
@committed
|
119
|
-
end
|
120
|
-
|
121
|
-
# Returns true if the Transaction object has already been aborted, false
|
122
|
-
# otherwise.
|
123
|
-
def aborted?
|
124
|
-
@aborted
|
125
|
-
end
|
126
|
-
|
127
|
-
# Similar to Stomper::Client#transaction, this method creates a new
|
128
|
-
# Transaction object, nested inside of this one. To prevent name
|
129
|
-
# collisions, this method automatically generates a transaction id,
|
130
|
-
# if one is not specified, of the form +<parent_transaction_id>-<Time.now.to_f>+.
|
131
|
-
def transaction(transaction_id=nil,&block)
|
132
|
-
# To get a transaction name guaranteed to not collide with this one
|
133
|
-
# we will supply an explicit id to the constructor unless an id was
|
134
|
-
# provided
|
135
|
-
transaction_id ||= "#{@id}-#{Time.now.to_f}"
|
136
|
-
self.class.new(@client, transaction_id, &block)
|
137
|
-
end
|
138
|
-
|
139
|
-
# Wraps the Stomper::Client#send method, injecting a "transaction" header
|
140
|
-
# into the +headers+ hash, thus informing the stomp broker that the message
|
141
|
-
# generated here is part of this transaction.
|
142
|
-
def send(destination, body, headers={})
|
143
|
-
@client.send(destination, body, headers.merge({:transaction => @id }))
|
144
|
-
end
|
145
|
-
|
146
|
-
# Wraps the Stomper::Client#ack method, injecting a "transaction" header
|
147
|
-
# into the +headers+ hash, thus informing the stomp broker that the message
|
148
|
-
# acknowledgement is part of this transaction.
|
149
|
-
def ack(message_or_id, headers={})
|
150
|
-
@client.ack(message_or_id, headers.merge({ :transaction => @id }))
|
151
|
-
end
|
152
|
-
|
153
|
-
# Aborts this transaction if it has not already been committed or aborted.
|
154
|
-
# Note that it does so by raising a TransactionAborted exception, allowing
|
155
|
-
# the +abort+ call to force any ancestral transactions to also fail.
|
156
|
-
#
|
157
|
-
# @see Transaction#commit
|
158
|
-
# @see Transaction#committed?
|
159
|
-
# @see Transaction#aborted?
|
160
|
-
def abort
|
161
|
-
raise TransactionAborted, "transaction '#{@id}' aborted explicitly" if _abort
|
162
|
-
end
|
163
|
-
|
164
|
-
# Commits this transaction unless it has already been committed or aborted.
|
165
|
-
#
|
166
|
-
# @see Transaction#abort
|
167
|
-
# @see Transaction#committed?
|
168
|
-
# @see Transaction#aborted?
|
169
|
-
def commit
|
170
|
-
# Guard against sending multiple commit messages to the server for a
|
171
|
-
# single transaction.
|
172
|
-
@client.commit(@id) unless committed? || aborted?
|
173
|
-
@committed = true
|
174
|
-
end
|
175
|
-
|
176
|
-
private
|
177
|
-
def _abort
|
178
|
-
# Guard against sending multiple abort messages to the server for a
|
179
|
-
# single transaction.
|
180
|
-
return false if committed? || aborted?
|
181
|
-
@client.abort(@id)
|
182
|
-
@aborted = true
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|