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,134 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Frame serializer / parser for STOMP 1.1 connections.
4
+ class OnStomp::Connections::Serializers::Stomp_1_1
5
+ include OnStomp::Connections::Serializers::Stomp_1
6
+
7
+ # Mapping of characters to their appropriate escape sequences. This
8
+ # is used when escaping headers for frames being written to the stream.
9
+ CHARACTER_ESCAPES = { ':' => "\\c", "\n" => "\\n", "\\" => "\\\\" }
10
+
11
+ # Mapping of escape sequences to their appropriate characters. This
12
+ # is used when unescaping headers being read from the stream.
13
+ ESCAPE_SEQUENCES = Hash[CHARACTER_ESCAPES.map { |k,v| [v,k] }]
14
+
15
+ # Creates a new serializer and calls {#reset_parser}
16
+ def initialize
17
+ reset_parser
18
+ end
19
+
20
+ # Converts a {OnStomp::Components::Frame frame} to a string
21
+ # @param [OnStomp::Components::Frame] frame
22
+ # @return [String]
23
+ def frame_to_string frame
24
+ frame_to_string_base(make_ct(frame)) do |k,v|
25
+ "#{escape_header k}:#{escape_header v}\n"
26
+ end
27
+ end
28
+
29
+ # Escapes a header name or value by replacing special characters with
30
+ # their appropriate escape sequences. The header will also be encoded to
31
+ # 'UTF-8' when using Ruby 1.9+
32
+ # @param [String] s header name or value
33
+ # @return [String]
34
+ def escape_header s
35
+ encode_header(s).gsub(/[:\n\\\\]/) { |c| CHARACTER_ESCAPES[c] }
36
+ end
37
+
38
+ # Unescapes a header name or pair parsed from the read buffer by converting
39
+ # known escape sequences into their special characters. The header string
40
+ # will have a 'UTF-8' encoding forced upon it when using Ruby 1.9+, as per
41
+ # the STOMP 1.1 spec.
42
+ # @param [String] s header name or value
43
+ # @return [String]
44
+ # @raise [OnStomp::InvalidHeaderEscapeSequenceError]
45
+ # if an unknown escape sequence is present in the header name or value
46
+ def unescape_header s
47
+ force_header_encoding(s).gsub(/\\.?/) do |c|
48
+ ESCAPE_SEQUENCES[c] || raise(OnStomp::InvalidHeaderEscapeSequenceError, "#{c}")
49
+ end
50
+ end
51
+
52
+ # Splits a header line into a header name / header value pair at the first
53
+ # ':' character {#unescape_header unescapes} them, and returns the pair.
54
+ # @param [String] str header line to split
55
+ # @return [[String, String]]
56
+ # @raise [OnStomp::MalformedHeaderError] if the header line
57
+ # lacks a ':' character
58
+ def split_header(str)
59
+ col = str.index(':')
60
+ unless col
61
+ raise OnStomp::MalformedHeaderError, "unterminated header: '#{str}'"
62
+ end
63
+ [ unescape_header(str[0...col]),
64
+ unescape_header(str[(col+1)..-1]) ]
65
+ end
66
+
67
+ # Forces the frame's body to match the charset specified in its +content-type+
68
+ # header, if applicable.
69
+ # @param [OnStomp::Components::Frame] frame
70
+ def prepare_parsed_frame frame
71
+ force_body_encoding frame
72
+ end
73
+
74
+ if RUBY_VERSION >= "1.9"
75
+ # Encodes the given string to 'UTF-8'
76
+ # @note No-op for Ruby 1.8.x
77
+ # @param [String] s
78
+ # @return [String]
79
+ def encode_header(s)
80
+ s.encoding.name == 'UTF-8' ? s : s.encode('UTF-8')
81
+ end
82
+ # Forces the encoding of the given string to 'UTF-8'
83
+ # @note No-op for Ruby 1.8.x
84
+ # @param [String] s
85
+ # @return [String]
86
+ def force_header_encoding(s); s.tap { s.force_encoding('UTF-8') }; end
87
+ # Forces the encoding of the given frame's body to match its charset.
88
+ # @note No-op for Ruby 1.8.x
89
+ # @param [OnStomp::Components::Frame] f
90
+ # @return [OnStomp::Components::Frame]
91
+ def force_body_encoding f
92
+ type, subtype, charset = f.content_type
93
+ charset ||= (type == 'text') ? 'UTF-8' : 'ASCII-8BIT'
94
+ f.body.force_encoding(charset)
95
+ f
96
+ end
97
+ # Set an appropriate +content-type+ header with +charset+ parameter for
98
+ # frames with a text body
99
+ # @note No-op for Ruby 1.8.x
100
+ # @param [OnStomp::Components::Frame] f
101
+ # @return [OnStomp::Components::Frame]
102
+ def make_ct f
103
+ return f if f.body.nil?
104
+ t, st = f.content_type
105
+ enc = f.body.encoding.name
106
+ if enc != 'ASCII-8BIT'
107
+ f[:'content-type'] = "#{t||'text'}/#{st||'plain'};charset=#{enc}"
108
+ end
109
+ f
110
+ end
111
+ else
112
+ # Encodes the given string to 'UTF-8'
113
+ # @note No-op for Ruby 1.8.x
114
+ # @param [String] s
115
+ # @return [String]
116
+ def encode_header(s); s; end
117
+ # Forces the encoding of the given string to 'UTF-8'
118
+ # @note No-op for Ruby 1.8.x
119
+ # @param [String] s
120
+ # @return [String]
121
+ def force_header_encoding(s); s; end
122
+ # Forces the encoding of the given frame's body to match its charset.
123
+ # @note No-op for Ruby 1.8.x
124
+ # @param [OnStomp::Components::Frame] f
125
+ # @return [OnStomp::Components::Frame]
126
+ def force_body_encoding(f); f; end
127
+ # Set an appropriate +content-type+ header with +charset+ parameter for
128
+ # frames with a text body
129
+ # @note No-op for Ruby 1.8.x
130
+ # @param [OnStomp::Components::Frame] f
131
+ # @return [OnStomp::Components::Frame]
132
+ def make_ct(f); f; end
133
+ end
134
+ end
@@ -0,0 +1,9 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Namespace for {OnStomp::Components::Frame frame} serializers / parsers.
4
+ module OnStomp::Connections::Serializers
5
+ end
6
+
7
+ require 'onstomp/connections/serializers/stomp_1'
8
+ require 'onstomp/connections/serializers/stomp_1_0'
9
+ require 'onstomp/connections/serializers/stomp_1_1'
@@ -0,0 +1,69 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Connection behavior common to both STOMP 1.0 and STOMP 1.1 connections
4
+ module OnStomp::Connections::Stomp_1
5
+ # Creates a CONNECT frame
6
+ # @return [OnStomp::Components::Frame] CONNECT frame
7
+ def connect_frame *h
8
+ create_frame 'CONNECT', h
9
+ end
10
+
11
+ # Creates a SEND frame
12
+ # @return [OnStomp::Components::Frame] SEND frame
13
+ def send_frame d, b, h
14
+ create_frame 'SEND', [h, {:destination => d}], b
15
+ end
16
+
17
+ # Creates a BEGIN frame
18
+ # @return [OnStomp::Components::Frame] BEGIN frame
19
+ def begin_frame tx, h
20
+ create_transaction_frame 'BEGIN', tx, h
21
+ end
22
+
23
+ # Creates a COMMIT frame
24
+ # @return [OnStomp::Components::Frame] COMMIT frame
25
+ def commit_frame tx, h
26
+ create_transaction_frame 'COMMIT', tx, h
27
+ end
28
+
29
+ # Creates an ABORT frame
30
+ # @return [OnStomp::Components::Frame] ABORT frame
31
+ def abort_frame tx, h
32
+ create_transaction_frame 'ABORT', tx, h
33
+ end
34
+
35
+ # Creates a DISCONNECT frame
36
+ # @return [OnStomp::Components::Frame] DISCONNECT frame
37
+ def disconnect_frame h
38
+ create_frame 'DISCONNECT', [h]
39
+ end
40
+
41
+ # Creates a SUBSCRIBE frame
42
+ # @return [OnStomp::Components::Frame] SUBSCRIBE frame
43
+ def subscribe_frame d, h
44
+ create_frame 'SUBSCRIBE', [{:id => OnStomp.next_serial}, h, {:destination => d}]
45
+ end
46
+
47
+ # Creates an UNSUBSCRIBE frame
48
+ # @return [OnStomp::Components::Frame] UNSUBSCRIBE frame
49
+ def unsubscribe_frame f, h
50
+ id = f.is_a?(OnStomp::Components::Frame) ? f[:id] : f
51
+ create_frame('UNSUBSCRIBE', [{:id => id}, h]).tap do |f|
52
+ raise ArgumentError, 'subscription ID could not be determined' unless f.header?(:id)
53
+ end
54
+ end
55
+
56
+ private
57
+ def create_transaction_frame command, tx, headers
58
+ create_frame command, [headers, {:transaction => tx}]
59
+ end
60
+
61
+ def create_frame command, layered_headers, body=nil
62
+ headers = layered_headers.inject({}) do |final, h|
63
+ h = OnStomp.keys_to_sym(h).delete_if { |k,v| final.key?(k) && (v.nil? || v.empty?) }
64
+ final.merge!(h)
65
+ final
66
+ end
67
+ OnStomp::Components::Frame.new(command, headers, body)
68
+ end
69
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # A STOMP 1.0 specific connection
4
+ class OnStomp::Connections::Stomp_1_0 < OnStomp::Connections::Base
5
+ include OnStomp::Connections::Stomp_1
6
+ # The serializer that will convert {OnStomp::Components::Frame frames} into
7
+ # raw bytes and will convert raw bytes into {OnStomp::Components::Frame frames}
8
+ # @return [OnStomp::Connections::Serializers::Stomp_1_0]
9
+ attr_reader :serializer
10
+
11
+ # Calls {OnStomp::Connections::Base#initialize} and creates a STOMP 1.0
12
+ # serializer
13
+ def initialize socket, client
14
+ super
15
+ @serializer = OnStomp::Connections::Serializers::Stomp_1_0.new
16
+ end
17
+
18
+ # Creates an ACK frame
19
+ # @return [OnStomp::Components::Frame] ACK frame
20
+ def ack_frame *args
21
+ headers = args.last.is_a?(Hash) ? args.pop : {}
22
+ m_id = args.shift
23
+ m_id = m_id[:'message-id'] if m_id.is_a?(OnStomp::Components::Frame)
24
+ create_frame('ACK', [{:'message-id' => m_id}, headers]).tap do |f|
25
+ raise ArgumentError, 'no message-id to ACK' unless f.header?(:'message-id')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,65 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # A STOMP 1.1 specific connection
4
+ class OnStomp::Connections::Stomp_1_1 < OnStomp::Connections::Base
5
+ include OnStomp::Connections::Stomp_1
6
+ include OnStomp::Connections::Heartbeating
7
+ # The serializer that will convert {OnStomp::Components::Frame frames} into
8
+ # raw bytes and will convert raw bytes into {OnStomp::Components::Frame frames}
9
+ # @return [OnStomp::Connections::Serializers::Stomp_1_1]
10
+ attr_reader :serializer
11
+
12
+ # Calls {OnStomp::Connections::Base#initialize} and creates a STOMP 1.0
13
+ # serializer
14
+ def initialize socket, client
15
+ super
16
+ @serializer = OnStomp::Connections::Serializers::Stomp_1_1.new
17
+ end
18
+
19
+ # Calls {OnStomp::Connections::Base#configure} then configures heartbeating
20
+ # parameters.
21
+ # @param [OnStomp::Components::Frame] connected
22
+ # @param [{Symbol => Proc}] con_cbs
23
+ def configure connected, con_cbs
24
+ super
25
+ configure_heartbeating client.heartbeats, connected.heart_beat
26
+ end
27
+
28
+ # Returns true if {OnStomp::Connections::Base#connected?} is true and
29
+ # we have a {#pulse?}
30
+ def connected?
31
+ super && pulse?
32
+ end
33
+
34
+ # Creates an ACK frame
35
+ # @return [OnStomp::Components::Frame] ACK frame
36
+ def ack_frame *args
37
+ create_ack_or_nack 'ACK', args
38
+ end
39
+
40
+ # Creates an NACK frame
41
+ # @return [OnStomp::Components::Frame] NACK frame
42
+ def nack_frame *args
43
+ create_ack_or_nack 'NACK', args
44
+ end
45
+
46
+ # Creates a heartbeat frame (serialized as a single "\n" character)
47
+ # @return [OnStomp::Components::Frame] heartbeat frame
48
+ def heartbeat_frame
49
+ OnStomp::Components::Frame.new
50
+ end
51
+
52
+ private
53
+ def create_ack_or_nack(command, args)
54
+ headers = args.last.is_a?(Hash) ? args.pop : {}
55
+ m_id = args.shift
56
+ sub_id = args.shift
57
+ if m_id.is_a?(OnStomp::Components::Frame)
58
+ sub_id = m_id[:subscription]
59
+ m_id = m_id[:'message-id']
60
+ end
61
+ create_frame(command, [{:'message-id' => m_id, :subscription => sub_id }, headers]).tap do |f|
62
+ raise ::ArgumentError, 'missing message-id or subscription headers' unless f.headers?(:'message-id', :subscription)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,119 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Namespace for protocol specific connections used to communicate with
4
+ # STOMP brokers.
5
+ module OnStomp::Connections
6
+ # Default SSL options to use when establishing an SSL connection.
7
+ DEFAULT_SSL_OPTIONS = {
8
+ :verify_mode => OpenSSL::SSL::VERIFY_PEER |
9
+ OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT,
10
+ :ca_file => nil,
11
+ :ca_path => nil,
12
+ :cert => nil,
13
+ :key => nil,
14
+ :post_connection_check => true
15
+ }
16
+
17
+ # Returns a list of supported protocol versions
18
+ # @return [Array<String>]
19
+ def self.supported
20
+ PROTOCOL_VERSIONS.keys.sort
21
+ end
22
+
23
+ # Filters a list of supplied versions to only those
24
+ # that are supported. Results are in the same order as they are found
25
+ # in {.supported}. If none of the supplied versions are supported, an
26
+ # empty list is returned.
27
+ # @param [Array<String>] vers versions to filter
28
+ # @return [Array<String>]
29
+ def self.select_supported vers
30
+ vers = Array(vers)
31
+ supported.select { |v| vers.include? v }
32
+ end
33
+
34
+ # Creates an initial {OnStomp::Connections::Stomp_1_0 connection} to
35
+ # the client's broker uri, performs the CONNECT/CONNECTED frame exchange,
36
+ # and returns a {OnStomp::Connections::Base connection} suitable for the
37
+ # negotiated STOMP protocol version.
38
+ # @param [OnStomp::Client] client
39
+ # @param [{#to_sym => #to_s}] u_head user specified headers for CONNECT frame
40
+ # @param [{#to_sym => #to_s}] c_head client specified headers for CONNECT frame
41
+ # @param [{Symbol => Proc}] con_cbs event callbacks to install on the final
42
+ # connection
43
+ # @return [OnStomp::Connections::Base] instance of Base subclass suited for
44
+ # negotiated protocol version
45
+ # @raise [OnStomp::OnStompError] if negotiating the connection raises an
46
+ # such an error.
47
+ def self.connect client, u_head, c_head, con_cbs
48
+ init_con = create_connection('1.0', nil, client)
49
+ ver, connected = init_con.connect client, u_head, c_head
50
+ begin
51
+ negotiate_connection(ver, init_con, client).tap do |final_con|
52
+ final_con.configure connected, con_cbs
53
+ end
54
+ rescue OnStomp::OnStompError
55
+ # Perform a blocking close.
56
+ init_con.close true
57
+ raise
58
+ end
59
+ end
60
+
61
+ private
62
+ def self.negotiate_connection vers, con, client
63
+ supports_protocol?(vers,con) ? con :
64
+ create_connection(vers, con.socket, client)
65
+ end
66
+
67
+ def self.supports_protocol? ver, con
68
+ con.is_a? PROTOCOL_VERSIONS[ver]
69
+ end
70
+
71
+ def self.create_connection ver, sock, client
72
+ unless sock
73
+ meth = client.ssl ? :ssl :
74
+ client.uri.respond_to?(:onstomp_socket_type) ?
75
+ client.uri.onstomp_socket_type : :tcp
76
+ sock = __send__(:"create_socket_#{meth}", client)
77
+ end
78
+ PROTOCOL_VERSIONS[ver].new sock, client
79
+ end
80
+
81
+ def self.create_socket_tcp client
82
+ TCPSocket.new(client.uri.host || 'localhost', client.uri.port)
83
+ end
84
+
85
+ def self.create_socket_ssl client
86
+ uri = client.uri
87
+ host = uri.host || 'localhost'
88
+ ssl_opts = client.ssl.is_a?(Hash) ? client.ssl : {}
89
+ ssl_opts = DEFAULT_SSL_OPTIONS.merge(ssl_opts)
90
+ context = OpenSSL::SSL::SSLContext.new
91
+ post_check = ssl_opts.delete(:post_connection_check)
92
+ post_check_host = (post_check == true) ? host : post_check
93
+ DEFAULT_SSL_OPTIONS.keys.each do |k|
94
+ context.__send__(:"#{k}=", ssl_opts[k]) if ssl_opts.key?(k)
95
+ end
96
+ tcp_sock = create_socket_tcp(client)
97
+ socket = OpenSSL::SSL::SSLSocket.new(tcp_sock, context)
98
+ socket.sync_close = true
99
+ socket.connect
100
+ socket.post_connection_check(post_check_host) if post_check_host
101
+ socket
102
+ end
103
+ end
104
+
105
+ require 'onstomp/connections/serializers'
106
+ require 'onstomp/connections/base'
107
+ require 'onstomp/connections/stomp_1'
108
+ require 'onstomp/connections/heartbeating'
109
+ require 'onstomp/connections/stomp_1_0'
110
+ require 'onstomp/connections/stomp_1_1'
111
+
112
+ module OnStomp::Connections
113
+ # A mapping of protocol versions to the connection classes that support
114
+ # them.
115
+ PROTOCOL_VERSIONS = {
116
+ '1.0' => OnStomp::Connections::Stomp_1_0,
117
+ '1.1' => OnStomp::Connections::Stomp_1_1
118
+ }
119
+ end
@@ -0,0 +1,55 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Module for configurable attributes specific to {OnStomp::Client client} objects.
4
+ module OnStomp::Interfaces::ClientConfigurable
5
+ # Includes {OnStomp::Interfaces::UriConfigurable} into +base+ and
6
+ # extends {OnStomp::Interfaces::ClientConfigurable::ClassMethods}
7
+ # @param [Module] base
8
+ def self.included(base)
9
+ base.__send__ :include, OnStomp::Interfaces::UriConfigurable
10
+ base.extend ClassMethods
11
+ end
12
+
13
+ # Provides attribute methods for {OnStomp::Client client} objects.
14
+ module ClassMethods
15
+ # Creates a readable and writeable attribute with the given name that
16
+ # defaults to the {OnStomp::Connections.supported supported} protocol
17
+ # versions and is {OnStomp::Connections.select_supported filtered} to
18
+ # those versions when assigned. Corresponds to which protocol versions
19
+ # should be used for a given client's connection.
20
+ # @param [Symbol] nm name of attribute
21
+ def attr_configurable_protocols nm
22
+ attr_configurable_arr(nm, :default => OnStomp::Connections.supported) do |vers|
23
+ OnStomp::Connections.select_supported(vers).tap do |valid|
24
+ raise OnStomp::UnsupportedProtocolVersionError, vers.inspect if valid.empty?
25
+ end
26
+ end
27
+ end
28
+
29
+ # Creates a readable and writeable attribute with the given name that
30
+ # defaults to [0, 0] and is mapped to a pair of non-negative integers
31
+ # when assigned. Corresponds to what heartbeating strategy should be used
32
+ # for a given client's connection where [0, 0] indicates no heartbeating
33
+ # should be performed.
34
+ # @note This attribute is only useful with STOMP 1.1 connections.
35
+ # @param [Symbol] nm name of attribute
36
+ def attr_configurable_client_beats nm
37
+ attr_configurable_arr(nm, :default => [0,0]) do |val|
38
+ val.map { |b| bi = b.to_i; bi < 0 ? 0 : bi }
39
+ end
40
+ end
41
+
42
+ # Creates a readable and writeable attribute with the given name that
43
+ # defaults to the {OnStomp::Components::ThreadedProcessor} and if set
44
+ # to nil will instead use {OnStomp::Components::NilProcessor}. Corresponds
45
+ # the the class to use when create new processor instances when a client
46
+ # is connected.
47
+ # @param [Symbol] nm name of attribute
48
+ def attr_configurable_processor nm
49
+ attr_configurable_class(nm,
50
+ :default => OnStomp::Components::ThreadedProcessor) do |pr|
51
+ pr || OnStomp::Components::NilProcessor
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,168 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Mixin for {OnStomp::Client client} events
4
+ # There are a few special event methods that will be passed on
5
+ # to the client's connection, they are:
6
+ # * +on_connection_established+ => {OnStomp::Interfaces::ConnectionEvents#on_established}
7
+ # * +on_connection_died+ => {OnStomp::Interfaces::ConnectionEvents#on_died}
8
+ # * +on_connection_terminated+ => {OnStomp::Interfaces::ConnectionEvents#on_terminated}
9
+ # * +on_connection_closed+ => {OnStomp::Interfaces::ConnectionEvents#on_closed}
10
+ module OnStomp::Interfaces::ClientEvents
11
+ include OnStomp::Interfaces::EventManager
12
+
13
+ # @group Client Frame Event Bindings
14
+
15
+ # Binds a callback to be invoked when an ACK frame is transmitted
16
+ # @yield [frame, client] callback invoked when event is triggered
17
+ # @yieldparam [OnStomp::Components::Frame] frame
18
+ # @yieldparam [OnStomp::Client] client
19
+ create_event_methods :ack, :before, :on
20
+ # Binds a callback to be invoked when a NACK frame is transmitted
21
+ # @yield [frame, client] callback invoked when event is triggered
22
+ # @yieldparam [OnStomp::Components::Frame] frame
23
+ # @yieldparam [OnStomp::Client] client
24
+ create_event_methods :nack, :before, :on
25
+ # Binds a callback to be invoked when a BEGIN frame is transmitted
26
+ # @yield [frame, client] callback invoked when event is triggered
27
+ # @yieldparam [OnStomp::Components::Frame] frame
28
+ # @yieldparam [OnStomp::Client] client
29
+ create_event_methods :begin, :before, :on
30
+ # Binds a callback to be invoked when an ABORT frame is transmitted
31
+ # @yield [frame, client] callback invoked when event is triggered
32
+ # @yieldparam [OnStomp::Components::Frame] frame
33
+ # @yieldparam [OnStomp::Client] client
34
+ create_event_methods :abort, :before, :on
35
+ # Binds a callback to be invoked when a COMMIT frame is transmitted
36
+ # @yield [frame, client] callback invoked when event is triggered
37
+ # @yieldparam [OnStomp::Components::Frame] frame
38
+ # @yieldparam [OnStomp::Client] client
39
+ create_event_methods :commit, :before, :on
40
+ # Binds a callback to be invoked when a SEND frame is transmitted
41
+ # @yield [frame, client] callback invoked when event is triggered
42
+ # @yieldparam [OnStomp::Components::Frame] frame
43
+ # @yieldparam [OnStomp::Client] client
44
+ create_event_methods :send, :before, :on
45
+ # Binds a callback to be invoked when a SUBSCRIBE frame is transmitted
46
+ # @yield [frame, client] callback invoked when event is triggered
47
+ # @yieldparam [OnStomp::Components::Frame] frame
48
+ # @yieldparam [OnStomp::Client] client
49
+ create_event_methods :subscribe, :before, :on
50
+ # Binds a callback to be invoked when an UNSUBSCRIBE frame is transmitted
51
+ # @yield [frame, client] callback invoked when event is triggered
52
+ # @yieldparam [OnStomp::Components::Frame] frame
53
+ # @yieldparam [OnStomp::Client] client
54
+ create_event_methods :unsubscribe, :before, :on
55
+ # Binds a callback to be invoked when a DISCONNECT frame is transmitted
56
+ # @yield [frame, client] callback invoked when event is triggered
57
+ # @yieldparam [OnStomp::Components::Frame] frame
58
+ # @yieldparam [OnStomp::Client] client
59
+ create_event_methods :disconnect, :before, :on
60
+ # Binds a callback to be invoked when a client heartbeat is transmitted
61
+ # @yield [frame, client] callback invoked when event is triggered
62
+ # @yieldparam [OnStomp::Components::Frame] frame
63
+ # @yieldparam [OnStomp::Client] client
64
+ create_event_methods :client_beat, :before, :on
65
+
66
+ # @group Broker Frame Event Bindings
67
+
68
+ # Binds a callback to be invoked when an ERROR frame is received
69
+ # @yield [frame, client] callback invoked when event is triggered
70
+ # @yieldparam [OnStomp::Components::Frame] frame
71
+ # @yieldparam [OnStomp::Client] client
72
+ create_event_methods :error, :before, :on
73
+ # Binds a callback to be invoked when a MESSAGE frame is received
74
+ # @yield [frame, client] callback invoked when event is triggered
75
+ # @yieldparam [OnStomp::Components::Frame] frame
76
+ # @yieldparam [OnStomp::Client] client
77
+ create_event_methods :message, :before, :on
78
+ # Binds a callback to be invoked when a RECEIPT frame is received
79
+ # @yield [frame, client] callback invoked when event is triggered
80
+ # @yieldparam [OnStomp::Components::Frame] frame
81
+ # @yieldparam [OnStomp::Client] client
82
+ create_event_methods :receipt, :before, :on
83
+ # Binds a callback to be invoked when a broker heartbeat is received
84
+ # @yield [frame, client] callback invoked when event is triggered
85
+ # @yieldparam [OnStomp::Components::Frame] frame
86
+ # @yieldparam [OnStomp::Client] client
87
+ create_event_methods :broker_beat, :before, :on
88
+
89
+ # @group Frame Exchange Event Bindings
90
+
91
+ # Binds a callback to be invoked when any frame is transmitted
92
+ # @yield [frame, client] callback invoked when event is triggered
93
+ # @yieldparam [OnStomp::Components::Frame] frame
94
+ # @yieldparam [OnStomp::Client] client
95
+ create_event_methods :transmitting, :before, :after
96
+ # Binds a callback to be invoked when any frame is received
97
+ # @yield [frame, client] callback invoked when event is triggered
98
+ # @yieldparam [OnStomp::Components::Frame] frame
99
+ # @yieldparam [OnStomp::Client] client
100
+ create_event_methods :receiving, :before, :after
101
+
102
+ # @endgroup
103
+
104
+ # Helpers for setting up connection events through a client
105
+ [:established, :terminated, :died, :closed].each do |ev|
106
+ module_eval <<-EOS
107
+ def on_connection_#{ev}(&cb)
108
+ if connection
109
+ connection.on_#{ev}(&cb)
110
+ else
111
+ pending_connection_events[:on_#{ev}] << cb
112
+ end
113
+ end
114
+ EOS
115
+ end
116
+
117
+ # Returns a hash of event bindings that should be set on a
118
+ # {OnStomp::Connections::Base connection}, but were set on the client
119
+ # because the connection does not exist yet.
120
+ # @return [{Symbol => Array<Proc>}]
121
+ def pending_connection_events
122
+ @pending_connection_events ||= Hash.new { |h,k| h[k] = [] }
123
+ end
124
+
125
+ # Triggers an event named by the supplied frame, prefixed with the supplied
126
+ # prefix. If the supplied frame is a 'heart-beat', origin will be used to
127
+ # dispatch appropriate heart-beat event (client_beat or broker_beat)
128
+ # @param [OnStomp::Components::Frame] f the frame trigger this event
129
+ # @param [:on, :before, etc] pref the prefix for the event name
130
+ # @param [:client, :broker] origin
131
+ def trigger_frame_event f, pref, origin
132
+ e = f.command ? :"#{pref}_#{f.command.downcase}" :
133
+ :"#{pref}_#{origin}_beat"
134
+ trigger_event e, f, self
135
+ end
136
+
137
+ # Triggers the :before_receiving event and the
138
+ # +before+ prefixed frame specific event (eg: +:before_error+).
139
+ # @param [OnStomp::Components::Frame] f
140
+ def trigger_before_receiving f
141
+ trigger_event :before_receiving, f, self
142
+ trigger_frame_event f, :before, :broker
143
+ end
144
+
145
+ # Triggers the :after_receiving event and the
146
+ # +on+ prefixed frame specific event (eg: +:on_message+)
147
+ # @param [OnStomp::Components::Frame] f
148
+ def trigger_after_receiving f
149
+ trigger_event :after_receiving, f, self
150
+ trigger_frame_event f, :on, :broker
151
+ end
152
+
153
+ # Triggers the :before_transmitting event and the
154
+ # +before+ prefixed frame specific event (eg: +:before_disconnect+).
155
+ # @param [OnStomp::Components::Frame] f
156
+ def trigger_before_transmitting f
157
+ trigger_event :before_transmitting, f, self
158
+ trigger_frame_event f, :before, :client
159
+ end
160
+
161
+ # Triggers the :after_transmitting event and the
162
+ # +on+ prefixed frame specific event (eg: +:on_send+).
163
+ # @param [OnStomp::Components::Frame] f
164
+ def trigger_after_transmitting f
165
+ trigger_event :after_transmitting, f, self
166
+ trigger_frame_event f, :on, :client
167
+ end
168
+ end