onstomp 1.0.0pre1

Sign up to get free protection for your applications and to get access to all the features.
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'