onstomp 1.0.0pre1

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 (80) hide show
  1. data/.autotest +2 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/.yardopts +5 -0
  5. data/CHANGELOG.md +4 -0
  6. data/DeveloperNarrative.md +15 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.md +221 -0
  9. data/README.md +73 -0
  10. data/Rakefile +6 -0
  11. data/UserNarrative.md +8 -0
  12. data/examples/basic.rb +40 -0
  13. data/examples/events.rb +72 -0
  14. data/lib/onstomp/client.rb +152 -0
  15. data/lib/onstomp/components/frame.rb +108 -0
  16. data/lib/onstomp/components/frame_headers.rb +212 -0
  17. data/lib/onstomp/components/nil_processor.rb +20 -0
  18. data/lib/onstomp/components/scopes/header_scope.rb +25 -0
  19. data/lib/onstomp/components/scopes/receipt_scope.rb +25 -0
  20. data/lib/onstomp/components/scopes/transaction_scope.rb +191 -0
  21. data/lib/onstomp/components/scopes.rb +45 -0
  22. data/lib/onstomp/components/subscription.rb +30 -0
  23. data/lib/onstomp/components/threaded_processor.rb +62 -0
  24. data/lib/onstomp/components/uri.rb +30 -0
  25. data/lib/onstomp/components.rb +13 -0
  26. data/lib/onstomp/connections/base.rb +208 -0
  27. data/lib/onstomp/connections/heartbeating.rb +82 -0
  28. data/lib/onstomp/connections/serializers/stomp_1.rb +166 -0
  29. data/lib/onstomp/connections/serializers/stomp_1_0.rb +41 -0
  30. data/lib/onstomp/connections/serializers/stomp_1_1.rb +134 -0
  31. data/lib/onstomp/connections/serializers.rb +9 -0
  32. data/lib/onstomp/connections/stomp_1.rb +69 -0
  33. data/lib/onstomp/connections/stomp_1_0.rb +28 -0
  34. data/lib/onstomp/connections/stomp_1_1.rb +65 -0
  35. data/lib/onstomp/connections.rb +119 -0
  36. data/lib/onstomp/interfaces/client_configurable.rb +55 -0
  37. data/lib/onstomp/interfaces/client_events.rb +168 -0
  38. data/lib/onstomp/interfaces/connection_events.rb +62 -0
  39. data/lib/onstomp/interfaces/event_manager.rb +69 -0
  40. data/lib/onstomp/interfaces/frame_methods.rb +190 -0
  41. data/lib/onstomp/interfaces/receipt_manager.rb +33 -0
  42. data/lib/onstomp/interfaces/subscription_manager.rb +48 -0
  43. data/lib/onstomp/interfaces/uri_configurable.rb +106 -0
  44. data/lib/onstomp/interfaces.rb +14 -0
  45. data/lib/onstomp/version.rb +13 -0
  46. data/lib/onstomp.rb +147 -0
  47. data/onstomp.gemspec +29 -0
  48. data/spec/onstomp/client_spec.rb +265 -0
  49. data/spec/onstomp/components/frame_headers_spec.rb +163 -0
  50. data/spec/onstomp/components/frame_spec.rb +144 -0
  51. data/spec/onstomp/components/nil_processor_spec.rb +32 -0
  52. data/spec/onstomp/components/scopes/header_scope_spec.rb +27 -0
  53. data/spec/onstomp/components/scopes/receipt_scope_spec.rb +33 -0
  54. data/spec/onstomp/components/scopes/transaction_scope_spec.rb +227 -0
  55. data/spec/onstomp/components/scopes_spec.rb +63 -0
  56. data/spec/onstomp/components/subscription_spec.rb +58 -0
  57. data/spec/onstomp/components/threaded_processor_spec.rb +92 -0
  58. data/spec/onstomp/components/uri_spec.rb +33 -0
  59. data/spec/onstomp/connections/base_spec.rb +349 -0
  60. data/spec/onstomp/connections/heartbeating_spec.rb +132 -0
  61. data/spec/onstomp/connections/serializers/stomp_1_0_spec.rb +50 -0
  62. data/spec/onstomp/connections/serializers/stomp_1_1_spec.rb +99 -0
  63. data/spec/onstomp/connections/serializers/stomp_1_spec.rb +104 -0
  64. data/spec/onstomp/connections/stomp_1_0_spec.rb +54 -0
  65. data/spec/onstomp/connections/stomp_1_1_spec.rb +137 -0
  66. data/spec/onstomp/connections/stomp_1_spec.rb +113 -0
  67. data/spec/onstomp/connections_spec.rb +135 -0
  68. data/spec/onstomp/interfaces/client_events_spec.rb +108 -0
  69. data/spec/onstomp/interfaces/connection_events_spec.rb +55 -0
  70. data/spec/onstomp/interfaces/event_manager_spec.rb +72 -0
  71. data/spec/onstomp/interfaces/frame_methods_spec.rb +109 -0
  72. data/spec/onstomp/interfaces/receipt_manager_spec.rb +53 -0
  73. data/spec/onstomp/interfaces/subscription_manager_spec.rb +64 -0
  74. data/spec/onstomp_spec.rb +15 -0
  75. data/spec/spec_helper.rb +12 -0
  76. data/spec/support/custom_argument_matchers.rb +51 -0
  77. data/spec/support/frame_matchers.rb +88 -0
  78. data/spec/support/shared_frame_method_examples.rb +116 -0
  79. data/yard_extensions.rb +32 -0
  80. metadata +219 -0
@@ -0,0 +1,62 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Mixin for connection events
4
+ module OnStomp::Interfaces::ConnectionEvents
5
+ include OnStomp::Interfaces::EventManager
6
+
7
+ # @group Connection State Events
8
+
9
+ # Binds a callback to be invoked when a connection has been fully
10
+ # established between broker and client.
11
+ # @yield [client, connection] callback invoked when event is triggered
12
+ # @yieldparam [OnStomp::Client] client
13
+ # @yieldparam [OnStomp::Connections::Base] connection that triggered
14
+ # the event (in general the same as +client.connection+)
15
+ create_event_methods :established, :on
16
+ # Binds a callback to be invoked when a connection has been died due to
17
+ # insufficient data transfer.
18
+ # @note Only applies to STOMP 1.1 connections with heartbeating enabled.
19
+ # @yield [client, connection] callback invoked when event is triggered
20
+ # @yieldparam [OnStomp::Client] client
21
+ # @yieldparam [OnStomp::Connections::Base] connection that triggered
22
+ # the event (in general the same as +client.connection+)
23
+ create_event_methods :died, :on
24
+ # Binds a callback to be invoked when a connection has been terminated
25
+ # (eg: closed unexpectedly due to an exception)
26
+ # @yield [client, connection] callback invoked when event is triggered
27
+ # @yieldparam [OnStomp::Client] client
28
+ # @yieldparam [OnStomp::Connections::Base] connection that triggered
29
+ # the event (in general the same as +client.connection+)
30
+ create_event_methods :terminated, :on
31
+ # Binds a callback to be invoked when a connection has been closed, either
32
+ # through a graceful disconnect or unexpectedly.
33
+ # @note If connection is closed unexpectedly, {#on_died} is triggered first,
34
+ # followed by this event.
35
+ # @yield [client, connection] callback invoked when event is triggered
36
+ # @yieldparam [OnStomp::Client] client
37
+ # @yieldparam [OnStomp::Connections::Base] connection that triggered
38
+ # the event (in general the same as +client.connection+)
39
+ create_event_methods :closed, :on
40
+
41
+ # @endgroup
42
+
43
+ # Triggers a connection specific event.
44
+ # @param [Symbol] event name
45
+ def trigger_connection_event event
46
+ trigger_event :"on_#{event}", self.client, self
47
+ end
48
+
49
+ # Takes a hash of event bindings a {OnStomp::Client client} has stored
50
+ # and binds them to this connection, then triggers +on_established+.
51
+ # This allows users to add callbacks for
52
+ # connection events before the connection exist and have said callbacks
53
+ # installed once the connection is created.
54
+ # @param [{Symbol => Array<Proc>}] callbacks to install, keyed by event name
55
+ # @see OnStomp::Interfaces::ClientEvents#pending_connection_events
56
+ def install_bindings_from_client ev_hash
57
+ ev_hash.each do |ev, cbs|
58
+ cbs.each { |cb| bind_event(ev, cb) }
59
+ end
60
+ trigger_connection_event :established
61
+ end
62
+ end
@@ -0,0 +1,69 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Mixin for event management.
4
+ module OnStomp::Interfaces::EventManager
5
+ # Extends base with {OnStomp::Interfaces::EventManager::ClassMethods}
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ # Binds a +Proc+ to be invoked when the given +event_name+ is triggered.
11
+ # @param [Symbol] event_name
12
+ # @param [Proc] cb_proc
13
+ # @return [self]
14
+ def bind_event(event_name, cb_proc)
15
+ event_callbacks[event_name] << cb_proc
16
+ self
17
+ end
18
+
19
+ # Returns a hash of event names mapped to arrays of proc callbacks.
20
+ # @return [{Symbol => Array<Proc>}]
21
+ def event_callbacks
22
+ @event_callbacks ||= Hash.new { |h, k| h[k] = [] }
23
+ end
24
+
25
+ # Triggers an event by the given name, passing along any additional
26
+ # +args+ as parameters to the callback
27
+ # @param [Symbol] event_name event to trigger
28
+ # @param [Object, Object, ...] args
29
+ def trigger_event(event_name, *args)
30
+ event_callbacks[event_name].each { |cb| cb.call(*args) }
31
+ end
32
+
33
+ # Mixin to allow includers to define custom event methods
34
+ module ClassMethods
35
+ # Creates a convenience method for binding callbacks to the given
36
+ # event name.
37
+ # @param [Symbol] name
38
+ # @example
39
+ # class ExampleClass
40
+ # include OnStomp::Interfaces::EventManager
41
+ #
42
+ # create_event_method :do_event
43
+ # end
44
+ #
45
+ # example_obj.do_event { |arg1, arg2| ... }
46
+ def create_event_method name
47
+ module_eval "def #{name}(&block); bind_event(:#{name}, block); end"
48
+ end
49
+
50
+ # Creates convenience methods for binding callbacks to the given
51
+ # event name with a set of prefixes.
52
+ # @param [Symbol] name
53
+ # @param [Symbol, Symbol, ...] prefixes (eg: :on, :before, :after)
54
+ # @example
55
+ # class ExampleClass
56
+ # include OnStomp::Interfaces::EventManager
57
+ #
58
+ # create_event_methods :some_event, :before, :during, :after
59
+ # end
60
+ #
61
+ # example_obj.before_some_event { |arg| ... }
62
+ # example_obj.after_some_event { |arg| ... }
63
+ # example_obj.during_some_event { |arg| ... }
64
+ def create_event_methods name, *prefixes
65
+ prefixes << :on if prefixes.empty?
66
+ prefixes.each { |pre| create_event_method :"#{pre}_#{name}" }
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,190 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Mixin for {OnStomp::Client clients} to provide methods that create
4
+ # and transmit STOMP {OnStomp::Components::Frame frames}.
5
+ module OnStomp::Interfaces::FrameMethods
6
+ # Transmits a SEND frame generated by the client's connection
7
+ # @param [String] dest destination for the frame
8
+ # @param [String] body body of the frame
9
+ # @param [{#to_sym => #to_s}] headers additional headers to include in
10
+ # the frame
11
+ # @return [OnStomp::Components::Frame] SEND frame
12
+ # @yield [receipt] block to invoke when a RECEIPT frame is received for the
13
+ # transmitted SEND frame
14
+ # @yieldparam [OnStomp::Components::Frame] receipt RECEIPT for the frame
15
+ # @raise OnStomp::UnsupportedCommandError if the connection does not support
16
+ # SEND frames
17
+ def send dest, body, headers={}, &cb
18
+ transmit connection.send_frame(dest, body, headers), :receipt => cb
19
+ end
20
+ alias :puts :send
21
+
22
+ # Transmits a SUBSCRIBE frame generated by the client's connection. Depending
23
+ # upon the connection, a subscription can be set to various MESSAGE
24
+ # acknowledgement modes by setting the +:ack+ header.
25
+ # STOMP 1.0 and STOMP 1.1 connections support:
26
+ # * :ack => 'auto'
27
+ # The broker assumes that MESSAGE frames received through the
28
+ # subscription have been properly received, the client should NOT attempt
29
+ # to ACK (or NACK) any of the messages.
30
+ # * :ack => 'client'
31
+ # The broker assumes that MESSAGE frames should be acknowledged by the
32
+ # client through the use of ACK frames.
33
+ # STOMP 1.1 connections support:
34
+ # * :ack => 'client-individual'
35
+ # Upon receiving an ACK frame for a MESSAGE frame, some brokers will
36
+ # mark the MESSAGE frame and all those sent to the client before it
37
+ # as acknowledged. This mode indicates that each MESSAGE frame must
38
+ # be acknowledged by its own ACK frame for the broker can assume the
39
+ # MESSAGE frame has been received by the client.
40
+ # @param [String] dest destination for the frame
41
+ # @param [{#to_sym => #to_s}] headers additional headers to include in
42
+ # the frame
43
+ # @return [OnStomp::Components::Frame] SUBSCRIBE frame
44
+ # @yield [message] block to invoke for every MESSAGE frame received on the
45
+ # subscription
46
+ # @yieldparam [OnStomp::Components::Frame] message MESSAGE frame received on
47
+ # the subscription
48
+ # @raise OnStomp::UnsupportedCommandError if the connection does not support
49
+ # SUBSCRIBE frames
50
+ # @see #unsubscribe
51
+ # @see #ack
52
+ # @see #nack
53
+ def subscribe dest, headers={}, &cb
54
+ transmit connection.subscribe_frame(dest, headers), :subscribe => cb
55
+ end
56
+
57
+ # Transmits an UNSUBSCRIBE frame generated by the client's connection.
58
+ # @overload unsubscribe(subscribe_frame, headers={})
59
+ # Generates an UNSUBSCRIBE frame to match the given SUBSCRIBE frame
60
+ # @param [OnStomp::Components::Frame] subscribe_frame
61
+ # @param [{#to_sym => #to_s}] headers optional headers to include in
62
+ # the UNSUBSCRIBE frame
63
+ # @overload unsubscribe(id, headers={})
64
+ # Generates an UNSUBSCRIBE frame with the given id
65
+ # @param [String] id
66
+ # @param [{#to_sym => #to_s}] headers optional headers to include in
67
+ # the UNSUBSCRIBE frame
68
+ # @return [OnStomp::Components::Frame] UNSUBSCRIBE frame
69
+ # @raise OnStomp::UnsupportedCommandError if the connection does not support
70
+ # UNSUBSCRIBE frames
71
+ # @see #subscribe
72
+ def unsubscribe frame_or_id, headers={}
73
+ transmit connection.unsubscribe_frame(frame_or_id, headers)
74
+ end
75
+
76
+ # Transmits a BEGIN frame generated by the client's connection to start
77
+ # a transaction.
78
+ # @param [String] tx_id identifier for the transaction
79
+ # @param [{#to_sym => #to_s}] headers additional headers to include in
80
+ # the frame
81
+ # @return [OnStomp::Components::Frame] BEGIN frame
82
+ # @raise OnStomp::UnsupportedCommandError if the connection does not support
83
+ # BEGIN frames
84
+ # @see #abort
85
+ # @see #commit
86
+ def begin tx_id, headers={}
87
+ transmit connection.begin_frame(tx_id, headers)
88
+ end
89
+
90
+ # Transmits an ABORT frame generated by the client's connection to rollback
91
+ # a transaction.
92
+ # @param [String] tx_id identifier for the transaction
93
+ # @param [{#to_sym => #to_s}] headers additional headers to include in
94
+ # the frame
95
+ # @return [OnStomp::Components::Frame] ABORT frame
96
+ # @raise OnStomp::UnsupportedCommandError if the connection does not support
97
+ # ABORT frames
98
+ # @see #begin
99
+ # @see #commit
100
+ def abort tx_id, headers={}
101
+ transmit connection.abort_frame(tx_id, headers)
102
+ end
103
+
104
+ # Transmits a COMMIT frame generated by the client's connection to complete
105
+ # a transaction.
106
+ # @param [String] tx_id identifier for the transaction
107
+ # @param [{#to_sym => #to_s}] headers additional headers to include in
108
+ # the frame
109
+ # @return [OnStomp::Components::Frame] COMMIT frame
110
+ # @raise OnStomp::UnsupportedCommandError if the connection does not support
111
+ # COMMIT frames
112
+ # @see #abort
113
+ # @see #begin
114
+ def commit tx_id, headers={}
115
+ transmit connection.commit_frame(tx_id, headers)
116
+ end
117
+
118
+ # Transmits a DISCONNECT frame generated by the client's connection to end
119
+ # the STOMP session.
120
+ # @param [{#to_sym => #to_s}] headers additional headers to include in
121
+ # the frame
122
+ # @return [OnStomp::Components::Frame] DISCONNECT frame
123
+ # @raise OnStomp::UnsupportedCommandError if the connection does not support
124
+ # DISCONNECT frames
125
+ def disconnect headers={}
126
+ transmit connection.disconnect_frame headers
127
+ end
128
+
129
+ # Transmits an ACK frame generated by the client's connection.
130
+ # @overload ack(message_frame, headers={})
131
+ # @note Users should use this form whenever possible as it will work
132
+ # with STOMP 1.0 and 1.1 connections.
133
+ # @param [OnStomp::Components::Frame] message_frame the MESSAGE frame to
134
+ # acknowledge.
135
+ # @param [{#to_sym => #to_s}] headers additional headers to include in
136
+ # the frame
137
+ # @overload ack(message_id, headers={})
138
+ # @note This form will raise an +ArgumentError+ with STOMP 1.1 connections
139
+ # as a subscription ID is also required to ACK a received MESSAGE.
140
+ # @param [String] message_id +message-id+ header of MESSAGE frame to
141
+ # acknowledge.
142
+ # @param [{#to_sym => #to_s}] headers additional headers to include in
143
+ # the frame
144
+ # @overload ack(message_id, subscription_id, heders={})
145
+ # @note This form should be used with STOMP 1.1 connections when it is
146
+ # not possible to provide the actual MESSAGE frame.
147
+ # @param [String] message_id +message-id+ header of MESSAGE frame to
148
+ # acknowledge.
149
+ # @param [String] subscription_id +subscription+ header of MESSAGE frame to
150
+ # acknowledge.
151
+ # @param [{#to_sym => #to_s}] headers additional headers to include in
152
+ # the frame
153
+ # @return [OnStomp::Components::Frame] ACK frame
154
+ # @raise OnStomp::UnsupportedCommandError if the connection does not support
155
+ # ACK frames
156
+ # @see #nack
157
+ def ack *args
158
+ transmit connection.ack_frame(*args)
159
+ end
160
+
161
+ # Transmits a NACK frame generated by the client's connection.
162
+ # @overload nack(message_frame, headers={})
163
+ # Generates a NACK frame for the given MESSAGE frame.
164
+ # @param [OnStomp::Components::Frame] message_frame the MESSAGE frame to
165
+ # un-acknowledge.
166
+ # @param [{#to_sym => #to_s}] headers additional headers to include in
167
+ # the frame
168
+ # @overload nack(message_id, subscription_id, heders={})
169
+ # @param [String] message_id +message-id+ header of MESSAGE frame to
170
+ # un-acknowledge.
171
+ # @param [String] subscription_id +subscription+ header of MESSAGE frame to
172
+ # un-acknowledge.
173
+ # @param [{#to_sym => #to_s}] headers additional headers to include in
174
+ # the frame
175
+ # @return [OnStomp::Components::Frame] NACK frame
176
+ # @raise OnStomp::UnsupportedCommandError if the connection does not support
177
+ # NACK frames
178
+ # @see #ack
179
+ def nack *args
180
+ transmit connection.nack_frame(*args)
181
+ end
182
+
183
+ # Transmits a client heartbeat frame generated by the client's connection.
184
+ # @return [OnStomp::Components::Frame] heartbeat frame
185
+ # @raise OnStomp::UnsupportedCommandError if the connection does not support
186
+ # heartbeat frames
187
+ def beat
188
+ transmit connection.heartbeat_frame
189
+ end
190
+ end
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Mixin for {OnStomp::Client clients} to provide receipt management
4
+ module OnStomp::Interfaces::ReceiptManager
5
+ private
6
+ def configure_receipt_management
7
+ @receipt_monitor = Monitor.new
8
+ @receipt_backs = {}
9
+ before_disconnect do |d, con|
10
+ @receipt_to_close = d[:receipt] if d[:receipt]
11
+ end
12
+ on_receipt do |r, con|
13
+ dispatch_receipt r
14
+ close if r[:'receipt-id'] == @receipt_to_close
15
+ end
16
+ end
17
+
18
+ def add_receipt f, cb
19
+ f[:receipt] = OnStomp.next_serial unless f.header?(:receipt)
20
+ @receipt_monitor.synchronize { @receipt_backs[f[:receipt]] = cb }
21
+ self
22
+ end
23
+
24
+ def clear_receipts
25
+ @receipt_monitor.synchronize { @receipt_backs.clear }
26
+ end
27
+
28
+ def dispatch_receipt receipt
29
+ cb = @receipt_monitor.synchronize { @receipt_backs.delete(receipt[:'receipt-id']) }
30
+ cb && cb.call(receipt)
31
+ self
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Mixin for {OnStomp::Client clients} to provide receipt management
4
+ module OnStomp::Interfaces::SubscriptionManager
5
+ # Returns an array of {OnStomp::Components::Subscription} objects for all
6
+ # currently active subscriptions.
7
+ # @return [Array<OnStomp::Components::Subscription>]
8
+ def subscriptions
9
+ @subcription_mon.synchronize { @subscriptions.values }
10
+ end
11
+
12
+ private
13
+ def configure_subscription_management
14
+ @subcription_mon = Monitor.new
15
+ @subscriptions = {}
16
+ on_message { |m, c| dispatch_subscription m }
17
+ on_unsubscribe { |u, c| remove_subscription u[:id] }
18
+ end
19
+
20
+ def add_subscription f, cb
21
+ s_id = f[:id]
22
+ dest = f[:destination]
23
+ @subcription_mon.synchronize do
24
+ @subscriptions[s_id] = OnStomp::Components::Subscription.new(f, cb)
25
+ end
26
+ end
27
+
28
+ def remove_subscription sub_id
29
+ @subcription_mon.synchronize do
30
+ @subscriptions.delete sub_id
31
+ end
32
+ end
33
+
34
+ def clear_subscriptions
35
+ @subcription_mon.synchronize { @subscriptions.clear }
36
+ end
37
+
38
+ def dispatch_subscription m
39
+ if m.header? :subscription
40
+ sub = @subcription_mon.synchronize { @subscriptions[m[:subscription]] }
41
+ sub && sub.call(m)
42
+ else
43
+ @subcription_mon.synchronize do
44
+ @subscriptions.values.select { |sub| sub.include? m }
45
+ end.each { |sub| sub.call m }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,106 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Module for configurable attributes
4
+ module OnStomp::Interfaces::UriConfigurable
5
+ # Extends +base+ with {OnStomp::Interfaces::UriConfigurable::ClassMethods}
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ private
11
+ def configure_configurable hash_opts
12
+ config = OnStomp.keys_to_sym(CGI.parse(uri.query || '')).
13
+ merge(OnStomp.keys_to_sym(hash_opts))
14
+ self.class.config_attributes.each do |attr_name, attr_conf|
15
+ attr_val = config.key?(attr_name) ? config[attr_name] :
16
+ attr_conf.key?(:uri_attr) && uri.__send__(attr_conf[:uri_attr]) ||
17
+ attr_conf[:default]
18
+ __send__(:"#{attr_name}=", attr_val)
19
+ end
20
+ end
21
+
22
+ # Provides attribute methods that can be configured by URI attributes
23
+ # or query parameters.
24
+ module ClassMethods
25
+ # Creates a group readable and writeable attributes that can be set
26
+ # by a URI query parameter sharing the same name, a property of a URI or
27
+ # a default value. The value of this attribute will be transformed by
28
+ # invoking the given block, if one has been provided.
29
+ def attr_configurable *args, &block
30
+ opts = args.last.is_a?(Hash) ? args.pop : {}
31
+ args.each do |attr_name|
32
+ __init_config_attribute__ attr_name, opts
33
+ attr_reader attr_name
34
+ define_method :"#{attr_name}=" do |v|
35
+ instance_variable_set(:"@#{attr_name}", (block ? block.call(v) : v))
36
+ end
37
+ end
38
+ end
39
+
40
+ # Creates a group readable and writeable attributes that can be set
41
+ # by a URI query parameter sharing the same name, a property of a URI or
42
+ # a default value. The value of this attribute will be transformed by
43
+ # invoking the given block, if one has been provided. If the attributes
44
+ # created by this method are assigned an +Array+, only the first element
45
+ # will be used as their value.
46
+ def attr_configurable_single *args, &block
47
+ trans = __attr_configurable_wrap__ lambda { |v| v.is_a?(Array) ? v.first : v }, block
48
+ attr_configurable(*args, &trans)
49
+ end
50
+
51
+ # Creates a group readable and writeable attributes that can be set
52
+ # by a URI query parameter sharing the same name, a property of a URI or
53
+ # a default value. The value of this attribute will be transformed by
54
+ # invoking the given block, if one has been provided. The attributes
55
+ # created by this method will be treated as though they were created
56
+ # with {#attr_configurable_single} and will also be converted into Strings.
57
+ def attr_configurable_str *args, &block
58
+ trans = __attr_configurable_wrap__ lambda { |v| v.to_s }, block
59
+ attr_configurable_single(*args, &trans)
60
+ end
61
+
62
+ # Creates a group readable and writeable attributes that can be set
63
+ # by a URI query parameter sharing the same name, a property of a URI or
64
+ # a default value. The value of this attribute will be transformed by
65
+ # invoking the given block, if one has been provided. If the attributes
66
+ # created by this method are assigned a value that is not an +Array+, the
67
+ # value will be wrapped in an array.
68
+ def attr_configurable_arr *args, &block
69
+ trans = __attr_configurable_wrap__ lambda { |v| Array(v) }, block
70
+ attr_configurable(*args, &trans)
71
+ end
72
+
73
+ # Creates a group readable and writeable attributes that can be set
74
+ # by a URI query parameter sharing the same name, a property of a URI or
75
+ # a default value. The value of this attribute will be transformed by
76
+ # invoking the given block, if one has been provided. The attributes
77
+ # created by this method will be treated as though they were created
78
+ # with {#attr_configurable_single} and will also be converted into Class
79
+ # or Module objects.
80
+ def attr_configurable_class *args, &block
81
+ trans = __attr_configurable_wrap__ lambda { |v| OnStomp.constantize(v) }, block
82
+ attr_configurable_single(*args, &trans)
83
+ end
84
+
85
+ private
86
+ def __attr_configurable_wrap__(trans, block)
87
+ if block
88
+ lambda { |v| block.call(trans.call(v)) }
89
+ else
90
+ trans
91
+ end
92
+ end
93
+
94
+ def __init_config_attribute__(name, opts)
95
+ unless respond_to?(:config_attributes)
96
+ class << self
97
+ def config_attributes
98
+ @config_attributes ||= {}
99
+ end
100
+ end
101
+ end
102
+ config_attributes[name] ||= {}
103
+ config_attributes[name].merge!(opts)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,14 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Namespace for interface mixins
4
+ module OnStomp::Interfaces
5
+ end
6
+
7
+ require 'onstomp/interfaces/uri_configurable'
8
+ require 'onstomp/interfaces/client_configurable'
9
+ require 'onstomp/interfaces/frame_methods'
10
+ require 'onstomp/interfaces/event_manager'
11
+ require 'onstomp/interfaces/connection_events'
12
+ require 'onstomp/interfaces/client_events'
13
+ require 'onstomp/interfaces/receipt_manager'
14
+ require 'onstomp/interfaces/subscription_manager'
@@ -0,0 +1,13 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Primary namespace for the +onstomp+ gem
4
+ module OnStomp
5
+ # Major / API version
6
+ MAJOR = 1
7
+ # Minor / feature version
8
+ MINOR = 0
9
+ # Patch version
10
+ PATCH = 0
11
+ # Complete version
12
+ VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}pre1"
13
+ end
data/lib/onstomp.rb ADDED
@@ -0,0 +1,147 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Copyright 2011 Ian D. Eccles
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ # For extensions to URI.parse for Stomp schemes.
18
+ require 'uri'
19
+ # Primarily for CGI.parse
20
+ require 'cgi'
21
+ # Sockets are fairly important in all of this.
22
+ require 'socket'
23
+ # As is openssl
24
+ require 'openssl'
25
+ # For IO#ready?
26
+ require 'io/wait'
27
+ # The socket helpers use this to delegate to the real sockets
28
+ require 'delegate'
29
+ # Threading and Mutex support
30
+ require 'thread'
31
+ # Monitor support (prevent recursive dead locking)
32
+ require 'monitor'
33
+
34
+ # Primary namespace for the +onstomp+ gem
35
+ module OnStomp
36
+ # A common base class for errors raised by the OnStomp gem
37
+ # @abstract
38
+ class OnStompError < StandardError; end
39
+
40
+ # Low level error raised when the broker transmits data that violates
41
+ # the Stomp protocol specification.
42
+ # @abstract
43
+ class FatalProtocolError < OnStompError; end
44
+
45
+ # Raised when an invalid character is encountered in a header
46
+ class InvalidHeaderCharacterError < FatalProtocolError; end
47
+
48
+ # Raised when an invalid escape sequence is encountered in a header name or value
49
+ class InvalidHeaderEscapeSequenceError < FatalProtocolError; end
50
+
51
+ # Raised when a malformed header is encountered. For example, if a header
52
+ # line does not contain ':'
53
+ class MalformedHeaderError < FatalProtocolError; end
54
+
55
+ # Raised when a malformed frame is encountered on the stream. For example,
56
+ # if a frame is not properly terminated with the {OnStomp::FrameIO::FRAME_TERMINATOR}
57
+ # character.
58
+ class MalformedFrameError < FatalProtocolError; end
59
+
60
+ # An error that is raised as a result of a misconfiguration of the client
61
+ # connection
62
+ # @abstract
63
+ class FatalConnectionError < OnStompError; end
64
+
65
+ # Raised when a connection has been configured with an unsupported protocol
66
+ # version. This can be due to end user misconfiguration, or due to improper
67
+ # protocol negotiation with the message broker.
68
+ class UnsupportedProtocolVersionError < FatalConnectionError; end
69
+
70
+ # Raised when an attempt to connect to the broker results in an unexpected
71
+ # exchange.
72
+ class ConnectFailedError < FatalConnectionError; end
73
+
74
+ # Raised if the command issued is not supported by the protocol version
75
+ # negotiated between the client and broker.
76
+ class UnsupportedCommandError < OnStompError; end
77
+
78
+ # An error that is raised as a result frames being generated on
79
+ # a transaction while it is in an invalid state.
80
+ # @abstract
81
+ class TransactionError < OnStompError; end
82
+
83
+ # Raised by ThreadedReceiver to stop the receiving thread.
84
+ class StopReceiver < StandardError; end
85
+
86
+ class << self
87
+ # Creates a new connection and immediately connects it to the broker.
88
+ # @see #initialize
89
+ def connect(uri, options={})
90
+ conx = OnStomp::Client.new(uri, options)
91
+ conx.connect
92
+ conx
93
+ end
94
+ alias :open :connect
95
+
96
+ # Duplicates an existing hash while transforming its keys to symbols.
97
+ # The keys must implement the +to_sym+ method, otherwise an exception will
98
+ # be raised. This method is used internally to convert hashes keyed with
99
+ # Strings.
100
+ #
101
+ # @param [{Object => Object}] hsh The hash to convert. It's keys must respond to +to_sym+.
102
+ # @return [{Symbol => Object}]
103
+ # @example
104
+ # hash = { '10' => nil, 'key2' => [3, 5, 8, 13, 21], :other => :value }
105
+ # OnStomp.keys_to_sym(hash) #=> { :'10' => nil, :key2 => [3, 5, 8, 13, 21], :other => :value }
106
+ # hash #=> { '10' => nil, 'key2' => [3, 5, 8, 13, 21], :other => :value }
107
+ def keys_to_sym(hsh)
108
+ hsh.inject({}) do |new_hash, (k,v)|
109
+ new_hash[k.to_sym] = v
110
+ new_hash
111
+ end
112
+ end
113
+
114
+ # Generates the next serial number in a thread-safe manner. This method
115
+ # merely initializes an instance variable to 0 if it has not been set,
116
+ # then increments this value and returns its string representation.
117
+ def next_serial(prefix=nil)
118
+ Thread.exclusive do
119
+ @next_serial_sequence ||= 0
120
+ @next_serial_sequence += 1
121
+ @next_serial_sequence.to_s
122
+ end
123
+ end
124
+
125
+ # Converts a string to the Ruby constant it names. If the +klass+ parameter
126
+ # is a kind of +Module+, this method will return +klass+ directly.
127
+ # @param [String,Module] klass
128
+ # @return [Module]
129
+ # @example
130
+ # OnStomp.constantize('OnStomp::Frame') #=> OnStomp::Frame
131
+ # OnStomp.constantize('This::Constant::DoesNotExist) #=> raises NameError
132
+ # OnStomp.constantize(Symbol) #=> Symbol
133
+ def constantize(klass)
134
+ return klass if klass.is_a?(Module) || klass.nil? || klass.respond_to?(:new)
135
+ klass.to_s.split('::').inject(Object) do |const, named|
136
+ next const if named.empty?
137
+ const.const_defined?(named) ? const.const_get(named) :
138
+ const.const_missing(named)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ require 'onstomp/version'
144
+ require 'onstomp/interfaces'
145
+ require 'onstomp/components'
146
+ require 'onstomp/connections'
147
+ require 'onstomp/client'